From dcb12de4ddcb8baf4dd9f07e1dc3ef48e93d6ccc Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:27:24 +0900 Subject: [PATCH 001/516] =?UTF-8?q?Feat:=20Event=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/entity/Event.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/event/entity/Event.java diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java new file mode 100644 index 00000000..368b31dd --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -0,0 +1,57 @@ +package com.otakumap.domain.event.entity; + +import com.otakumap.domain.event.entity.enums.EventStatus; +import com.otakumap.domain.event.entity.enums.EventType; +import com.otakumap.domain.event.entity.enums.Genre; +import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Event extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String title; + + @Column(nullable = false, length = 50) + private String name; + + @Column(nullable = false) + private LocalDate startDate; + + @Column(nullable = false) + private LocalDate endDate; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(20)") + private EventType type; + + @Column(columnDefinition = "TEXT") + private String thumbnail; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private Genre genre; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(15)", nullable = false) + private EventStatus status; + + @Column(columnDefinition = "TEXT") + private String site; + + @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) + private List eventLikeList = new ArrayList<>(); +} From 70ae582cb0ad5125a1c4cfd5d7683d912088dc28 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:27:32 +0900 Subject: [PATCH 002/516] =?UTF-8?q?Feat:=20EventLike=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/entity/EventLike.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/event_like/entity/EventLike.java diff --git a/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java b/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java new file mode 100644 index 00000000..f21e906b --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java @@ -0,0 +1,35 @@ +package com.otakumap.domain.event_like.entity; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventLike extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "event_id", nullable = false) + private Event event; + + @Column(name = "is_favorite", nullable = false) + @ColumnDefault("false") + private Boolean isFavorite; +} From 99c8d05bfde6290560f1f7fe4d60ee8db2c77d13 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:29:05 +0900 Subject: [PATCH 003/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EC=A0=80=EC=9E=A5=ED=95=9C=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/OtakumapApplication.java | 2 + .../event/repository/EventRepository.java | 7 ++ .../controller/EventLikeController.java | 47 +++++++++++++ .../converter/EventLikeConverter.java | 29 ++++++++ .../event_like/dto/EventLikeResponseDTO.java | 37 ++++++++++ .../repository/EventLikeRepository.java | 17 +++++ .../service/EventLikeCommandService.java | 7 ++ .../service/EventLikeCommandServiceImpl.java | 24 +++++++ .../service/EventLikeQueryService.java | 8 +++ .../service/EventLikeQueryServiceImpl.java | 69 +++++++++++++++++++ .../user/repository/UserRepository.java | 7 ++ .../validator/EventLikeExistValidator.java | 38 ++++++++++ 12 files changed, 292 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/event/repository/EventRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java create mode 100644 src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java create mode 100644 src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java create mode 100644 src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java create mode 100644 src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/user/repository/UserRepository.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/EventLikeExistValidator.java diff --git a/src/main/java/com/otakumap/OtakumapApplication.java b/src/main/java/com/otakumap/OtakumapApplication.java index 45e6cead..1ed786ae 100644 --- a/src/main/java/com/otakumap/OtakumapApplication.java +++ b/src/main/java/com/otakumap/OtakumapApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class OtakumapApplication { diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java new file mode 100644 index 00000000..f47975b5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event.repository; + +import com.otakumap.domain.event.entity.Event; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java new file mode 100644 index 00000000..755ee506 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -0,0 +1,47 @@ +package com.otakumap.domain.event_like.controller; + +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.event_like.service.EventLikeCommandService; +import com.otakumap.domain.event_like.service.EventLikeQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ExistEventLike; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@Validated +public class EventLikeController { + private final EventLikeQueryService eventLikeQueryService; + private final EventLikeCommandService eventLikeCommandService; + + // 로그인 시 엔드포인트 변경 예정 + @Operation(summary = "저장된 이벤트 목록 조회", description = "저장된 이벤트 목록을 불러옵니다.") + @GetMapping( "/users/{userId}/saved-events") + @Parameters({ + @Parameter(name = "userId", description = "사용자 ID"), + @Parameter(name = "type", description = "이벤트 타입 -> 1: 팝업 스토어, 2: 전시회, 3: 콜라보 카페"), + @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), + @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") + }) + public ApiResponse getEventLikeList(@PathVariable Long userId, @RequestParam(required = false) Integer type, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(userId, type, lastId, limit)); + } + + @Operation(summary = "저장된 이벤트 삭제", description = "저장된 이벤트를 삭제합니다.") + @DeleteMapping("/saved-events") + @Parameters({ + @Parameter(name = "eventIds", description = "저장된 이벤트 id List"), + }) + public ApiResponse deleteEventLike(@RequestParam(required = false) @ExistEventLike List eventIds) { + eventLikeCommandService.deleteEventLike(eventIds); + return ApiResponse.onSuccess("저장된 이벤트가 성공적으로 삭제되었습니다"); + } +} diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java new file mode 100644 index 00000000..a329094a --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.event_like.converter; + +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.event_like.entity.EventLike; + +import java.util.List; + +public class EventLikeConverter { + public static EventLikeResponseDTO.EventLikePreViewDTO eventLikePreViewDTO(EventLike eventLike) { + return EventLikeResponseDTO.EventLikePreViewDTO.builder() + .id(eventLike.getId()) + .eventId(eventLike.getEvent().getId()) + .name(eventLike.getEvent().getName()) + .thumbnail(eventLike.getEvent().getThumbnail()) + .startDate(eventLike.getEvent().getStartDate()) + .endDate(eventLike.getEvent().getEndDate()) + .isFavorite(eventLike.getIsFavorite()) + .eventType(eventLike.getEvent().getType()) + .build(); + + } + public static EventLikeResponseDTO.EventLikePreViewListDTO eventLikePreViewListDTO(List eventLikes, boolean hasNext, Long lastId) { + return EventLikeResponseDTO.EventLikePreViewListDTO.builder() + .eventLikes(eventLikes) + .hasNext(hasNext) + .lastId(lastId) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java new file mode 100644 index 00000000..40a7c02e --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java @@ -0,0 +1,37 @@ +package com.otakumap.domain.event_like.dto; + +import com.otakumap.domain.event.entity.enums.EventType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +public class EventLikeResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventLikePreViewDTO { + Long id; + Long eventId; + String name; + String thumbnail; + LocalDate startDate; + LocalDate endDate; + Boolean isFavorite; + EventType eventType; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventLikePreViewListDTO { + List eventLikes; + boolean hasNext; + Long lastId; + } +} diff --git a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java new file mode 100644 index 00000000..9efed967 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java @@ -0,0 +1,17 @@ +package com.otakumap.domain.event_like.repository; + +import com.otakumap.domain.event.entity.enums.EventType; +import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; + +public interface EventLikeRepository extends JpaRepository { + Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); + Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); + Page findAllByUserIsAndEventTypeOrderByCreatedAtDesc(User user, EventType type, Pageable pageable); + Page findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(User user, EventType type, LocalDateTime createdAt, Pageable pageable); +} diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java new file mode 100644 index 00000000..75959971 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event_like.service; + +import java.util.List; + +public interface EventLikeCommandService { + void deleteEventLike(List eventIds); +} diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java new file mode 100644 index 00000000..839d81e0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -0,0 +1,24 @@ +package com.otakumap.domain.event_like.service; + +import com.otakumap.domain.event_like.repository.EventLikeRepository; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class EventLikeCommandServiceImpl implements EventLikeCommandService { + private final EventLikeRepository eventLikeRepository; + private final EntityManager entityManager; + + @Override + public void deleteEventLike(List eventIds) { + eventLikeRepository.deleteAllByIdInBatch(eventIds); + entityManager.flush(); + entityManager.clear(); + } +} diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java new file mode 100644 index 00000000..59630cda --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.event_like.service; + +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; + +public interface EventLikeQueryService { + EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId, Integer type, Long lastId, int limit); + boolean isEventLikeExist(Long id); +} diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java new file mode 100644 index 00000000..81d2e894 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java @@ -0,0 +1,69 @@ +package com.otakumap.domain.event_like.service; + +import com.otakumap.domain.event.entity.enums.EventType; +import com.otakumap.domain.event_like.converter.EventLikeConverter; +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.event_like.repository.EventLikeRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EventLikeQueryServiceImpl implements EventLikeQueryService { + private final EventLikeRepository eventLikeRepository; + private final UserRepository userRepository; + + @Override + public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId, Integer type, Long lastId, int limit) { + List result; + Pageable pageable = PageRequest.of(0, limit + 1); + User user = userRepository.findById(userId).orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + EventType eventType = (type == null || type == 0) ? null : EventType.values()[type - 1]; + + if (lastId.equals(0L)) { + result = (eventType == null) + ? eventLikeRepository.findAllByUserIsOrderByCreatedAtDesc(user, pageable).getContent() + : eventLikeRepository.findAllByUserIsAndEventTypeOrderByCreatedAtDesc(user, eventType, pageable).getContent(); + } else { + EventLike eventLike = eventLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_LIKE_NOT_FOUND)); + result = (eventType == null) + ? eventLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventLike.getCreatedAt(), pageable).getContent() + : eventLikeRepository.findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventType, eventLike.getCreatedAt(), pageable).getContent(); + } + return createEventLikePreviewListDTO(user, result, limit); + } + + + private EventLikeResponseDTO.EventLikePreViewListDTO createEventLikePreviewListDTO(User user, List eventLikes, int limit) { + boolean hasNext = eventLikes.size() > limit; + Long lastId = null; + if (hasNext) { + eventLikes = eventLikes.subList(0, eventLikes.size() - 1); + lastId = eventLikes.get(eventLikes.size() - 1).getId(); + } + List list = eventLikes + .stream() + .map(EventLikeConverter::eventLikePreViewDTO) + .collect(Collectors.toList()); + + return EventLikeConverter.eventLikePreViewListDTO(list, hasNext, lastId); + } + + @Override + public boolean isEventLikeExist(Long id) { + return eventLikeRepository.existsById(id); + } +} diff --git a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java new file mode 100644 index 00000000..9cd7df16 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.user.repository; + +import com.otakumap.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/global/validation/validator/EventLikeExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/EventLikeExistValidator.java new file mode 100644 index 00000000..a107c056 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/EventLikeExistValidator.java @@ -0,0 +1,38 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.event_like.service.EventLikeQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistEventLike; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class EventLikeExistValidator implements ConstraintValidator> { + private final EventLikeQueryService eventLikeQueryService; + + @Override + public void initialize(ExistEventLike constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List eventIds, ConstraintValidatorContext context) { + if (eventIds == null || eventIds.isEmpty()) { + return false; + } + + boolean isValid = eventIds.stream() + .allMatch(eventId -> eventLikeQueryService.isEventLikeExist(eventId)); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.EVENT_LIKE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} \ No newline at end of file From 397a58cf8bbea48afdf23e47436a4e3451994f9c Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:30:16 +0900 Subject: [PATCH 004/516] =?UTF-8?q?Chore:=20swagger=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ee11d8e6..3a585cf6 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' // swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' } tasks.named('test') { From 5bd94aa2e79766631348a836c7842daf3a9f0f26 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:32:48 +0900 Subject: [PATCH 005/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/apiPayload/code/status/ErrorStatus.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 2526a3d9..eefd2038 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -16,8 +16,11 @@ public enum ErrorStatus implements BaseErrorCode { _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), - // 멤버 관려 에러 - MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."); + // 멤버 관련 에러 + USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), + + // 이벤트 좋아요 관련 에러 + EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."); private final HttpStatus httpStatus; From d6b3c2898d4bab90b52ac224e4c4b1fad51e52b4 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:33:06 +0900 Subject: [PATCH 006/516] =?UTF-8?q?Feat:=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20Handler=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/exception/handler/EventHandler.java | 10 ++++++++++ .../apiPayload/exception/handler/UserHandler.java | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java new file mode 100644 index 00000000..9630eb0b --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class EventHandler extends GeneralException { + public EventHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java new file mode 100644 index 00000000..f5e85062 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class UserHandler extends GeneralException { + public UserHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 6c34855e565fe68db1d8738654a580ed64923eb2 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:34:18 +0900 Subject: [PATCH 007/516] =?UTF-8?q?Feat:=20Event=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EA=B4=80=EB=A0=A8=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/entity/enums/EventStatus.java | 6 ++++++ .../domain/event/entity/enums/EventType.java | 12 ++++++++++++ .../otakumap/domain/event/entity/enums/Genre.java | 14 ++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java create mode 100644 src/main/java/com/otakumap/domain/event/entity/enums/EventType.java create mode 100644 src/main/java/com/otakumap/domain/event/entity/enums/Genre.java diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java b/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java new file mode 100644 index 00000000..36415e0a --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.event.entity.enums; + +public enum EventStatus { + NOT_STARTED, + IN_PROCESS +} diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/EventType.java b/src/main/java/com/otakumap/domain/event/entity/enums/EventType.java new file mode 100644 index 00000000..8e9bf885 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/enums/EventType.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.event.entity.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum EventType { + POPUP_STORE("팝업 스토어"), + EXHIBITION("전시회"), + COLLABORATION_CAFE("콜라보 카페"); + + private final String description; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java b/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java new file mode 100644 index 00000000..b48391ae --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.event.entity.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum Genre { + SUNJEONG("순정"), + ACTION("액션"), + FANTASY("판타지"), + THRILLER("스릴러"), + SPORTS("스포츠"); + + private final String description; +} From 9e336920d77cdd6127ed8b80e63c4d1b28ff2a44 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:34:32 +0900 Subject: [PATCH 008/516] =?UTF-8?q?Feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/user/entity/User.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/user/entity/User.java diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java new file mode 100644 index 00000000..ffc56efa --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -0,0 +1,59 @@ +package com.otakumap.domain.user.entity; + +import com.otakumap.domain.user.entity.enums.Role; +import com.otakumap.domain.user.entity.enums.SocialType; +import com.otakumap.domain.user.entity.enums.UserStatus; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "user") +public class User extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 20, nullable = false) + private String userId; + + @Column(length = 20, nullable = false) + private String password; + + @Column(length = 30, nullable = false) + private String email; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private SocialType socialType; + + @Column(length = 15, nullable = false) + private String name; + + @Column(length = 15) + private String nickname; + + @Column(columnDefinition = "TEXT") + private String profileImage; + + @ColumnDefault("0") + @Column(nullable = false) + private Integer donation; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'ACTIVE'", nullable = false) + private UserStatus status; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'USER'", nullable = false) + private Role role; +} From 8263252c35d24654013ddeb2a7d4964eb06d2cdd Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:34:47 +0900 Subject: [PATCH 009/516] =?UTF-8?q?Feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EA=B4=80=EB=A0=A8=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/user/entity/enums/Role.java | 6 ++++++ .../com/otakumap/domain/user/entity/enums/SocialType.java | 7 +++++++ .../com/otakumap/domain/user/entity/enums/UserStatus.java | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/user/entity/enums/Role.java create mode 100644 src/main/java/com/otakumap/domain/user/entity/enums/SocialType.java create mode 100644 src/main/java/com/otakumap/domain/user/entity/enums/UserStatus.java diff --git a/src/main/java/com/otakumap/domain/user/entity/enums/Role.java b/src/main/java/com/otakumap/domain/user/entity/enums/Role.java new file mode 100644 index 00000000..0318b23c --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/entity/enums/Role.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.user.entity.enums; + +public enum Role { + USER, + ADMIN +} diff --git a/src/main/java/com/otakumap/domain/user/entity/enums/SocialType.java b/src/main/java/com/otakumap/domain/user/entity/enums/SocialType.java new file mode 100644 index 00000000..48158ff8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/entity/enums/SocialType.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.user.entity.enums; + +public enum SocialType { + KAKAO, + GOOGLE, + NAVER +} diff --git a/src/main/java/com/otakumap/domain/user/entity/enums/UserStatus.java b/src/main/java/com/otakumap/domain/user/entity/enums/UserStatus.java new file mode 100644 index 00000000..1357bf3d --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/entity/enums/UserStatus.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.user.entity.enums; + +public enum UserStatus { + ACTIVE, + INACTIVE +} From a3daf655d6ecdd7fc798a41e473d96e65dd01589 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 19:35:42 +0900 Subject: [PATCH 010/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20@ExistEventL?= =?UTF-8?q?ike=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validation/annotation/ExistEventLike.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistEventLike.java diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistEventLike.java b/src/main/java/com/otakumap/global/validation/annotation/ExistEventLike.java new file mode 100644 index 00000000..f3285b69 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistEventLike.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.EventLikeExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = EventLikeExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistEventLike { + String message() default "유효하지 않은 이벤트 ID가 포함되어 있습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file From b13a74f277d47c1d011cb436a86cf0302c6d5234 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 10 Jan 2025 21:34:10 +0900 Subject: [PATCH 011/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=97=AC=EB=B6=80=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index ffc56efa..d21192f1 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -56,4 +56,8 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) @Column(columnDefinition = "VARCHAR(10) DEFAULT 'USER'", nullable = false) private Role role; + + @ColumnDefault("FALSE") + @Column(name = "is_email_verified") + private Boolean isEmailVerified; } From 214100fd83c90fc9b3fb135407f3f1c8b1f44daf Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 07:25:05 +0900 Subject: [PATCH 012/516] =?UTF-8?q?Feat:=20Event,=20EventLocation,=20Image?= =?UTF-8?q?,=20Event=EC=9D=98=20Enum=20=EC=B6=94=EA=B0=80=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/entity/Event.java | 59 +++++++++++++++++++ .../event/entity/enums/EventStatus.java | 6 ++ .../domain/event/entity/enums/EventType.java | 13 ++++ .../domain/event/entity/enums/Genre.java | 14 +++++ .../eventLocation/entity/EventLocation.java | 28 +++++++++ .../otakumap/domain/image/entity/Image.java | 26 ++++++++ src/main/resources/application.yml | 2 +- 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/event/entity/Event.java create mode 100644 src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java create mode 100644 src/main/java/com/otakumap/domain/event/entity/enums/EventType.java create mode 100644 src/main/java/com/otakumap/domain/event/entity/enums/Genre.java create mode 100644 src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java create mode 100644 src/main/java/com/otakumap/domain/image/entity/Image.java diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java new file mode 100644 index 00000000..2973fdc0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -0,0 +1,59 @@ +package com.otakumap.domain.event.entity; + +import com.otakumap.domain.event.entity.enums.EventStatus; +import com.otakumap.domain.event.entity.enums.EventType; +import com.otakumap.domain.event.entity.enums.Genre; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDate; + + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Event extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String title; + + @Column(nullable = false, length = 50) + private String name; + + @Column(nullable = false) + private LocalDate startDate; + + @Column(nullable = false) + private LocalDate endDate; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(20)") + private EventType type; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private Genre genre; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(15)", nullable = false) + private EventStatus status; + + @Column(columnDefinition = "TEXT") + private String site; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "thumbnail_image_id", referencedColumnName = "id") + private Image thumbnailImage; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "goods_image_id", referencedColumnName = "id") + private Image goodsImage; + +} diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java b/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java new file mode 100644 index 00000000..36415e0a --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.event.entity.enums; + +public enum EventStatus { + NOT_STARTED, + IN_PROCESS +} diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/EventType.java b/src/main/java/com/otakumap/domain/event/entity/enums/EventType.java new file mode 100644 index 00000000..6f65160b --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/enums/EventType.java @@ -0,0 +1,13 @@ +package com.otakumap.domain.event.entity.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum EventType { + POPUP_STORE("팝업 스토어"), + EXHIBITION("전시회"), + COLLABORATION_CAFE("콜라보 카페"); + + private final String description; + +} diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java b/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java new file mode 100644 index 00000000..d94f59bd --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.event.entity.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum Genre { + ROMANCE("순정"), + ACTION("액션"), + FANTASY("판타지"), + THRILLER("스릴러"), + SPORTS("스포츠"); + + private final String description; +} diff --git a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java new file mode 100644 index 00000000..727a6122 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.eventLocation.entity; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventLocation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(columnDefinition = "TEXT") + private String latitude; + + @Column(columnDefinition = "TEXT") + private String longitude; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "event_id", referencedColumnName = "id") + private Event event; +} diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java new file mode 100644 index 00000000..d1cdf151 --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -0,0 +1,26 @@ +package com.otakumap.domain.image.entity; + +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Image extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String uuid; + + @Column(nullable = false, length = 100) + private String fileName; + + @Column(nullable = false, length = 300) + private String fileUrl; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ab7ce13c..597e5cf6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,5 +15,5 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: update + auto: create default_batch_fetch_size: 1000 \ No newline at end of file From e70c2a1ef235f0fc7843f78def8a9d28a252d317 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 07:37:41 +0900 Subject: [PATCH 013/516] =?UTF-8?q?Feat:=20Event,=20EventLocation=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/event/entity/Event.java | 11 +++++++++-- .../domain/eventLocation/entity/EventLocation.java | 4 ++++ src/main/resources/application.yml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index 2973fdc0..cdbd69a5 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -21,12 +21,15 @@ public class Event extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 50) + @Column(nullable = false, length = 50) // 대표로 사용되는 이벤트 한글 제목 private String title; - @Column(nullable = false, length = 50) + @Column(nullable = false, length = 50) // 이벤트 일본어 원제 private String name; + @Column(nullable = false, length = 50) // 이벤트에 해당하는 애니메이션 이름 + private String animationName; + @Column(nullable = false) private LocalDate startDate; @@ -52,6 +55,10 @@ public class Event extends BaseEntity { @JoinColumn(name = "thumbnail_image_id", referencedColumnName = "id") private Image thumbnailImage; + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "backgroud_image_id", referencedColumnName = "id") + private Image backgroudImage; + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "goods_image_id", referencedColumnName = "id") private Image goodsImage; diff --git a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java index 727a6122..f3c0d882 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java +++ b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java @@ -1,6 +1,7 @@ package com.otakumap.domain.eventLocation.entity; import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.enums.EventStatus; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -16,6 +17,9 @@ public class EventLocation extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(columnDefinition = "VARCHAR(50)", nullable = false) + private String name; + @Column(columnDefinition = "TEXT") private String latitude; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 597e5cf6..ab7ce13c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,5 +15,5 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: create + auto: update default_batch_fetch_size: 1000 \ No newline at end of file From 982d061f4de62eb6e4c102407cba6220297254bc Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 11 Jan 2025 08:24:01 +0900 Subject: [PATCH 014/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index d21192f1..e05f4732 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -42,9 +42,6 @@ public class User extends BaseEntity { @Column(length = 15) private String nickname; - @Column(columnDefinition = "TEXT") - private String profileImage; - @ColumnDefault("0") @Column(nullable = false) private Integer donation; From f1554cac9bcf2c1808556d6b424d2294c84c65c1 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 11 Jan 2025 08:25:16 +0900 Subject: [PATCH 015/516] =?UTF-8?q?Refactor:=20ENUM=20=EA=B0=92=20SUNJUNG?= =?UTF-8?q?=20->=20ROMANCE=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/event/entity/enums/Genre.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java b/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java index b48391ae..d94f59bd 100644 --- a/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java +++ b/src/main/java/com/otakumap/domain/event/entity/enums/Genre.java @@ -4,7 +4,7 @@ @AllArgsConstructor public enum Genre { - SUNJEONG("순정"), + ROMANCE("순정"), ACTION("액션"), FANTASY("판타지"), THRILLER("스릴러"), From 207f5287f21eb3cf693fd7c190a289166c443d7d Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 09:26:08 +0900 Subject: [PATCH 016/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84(=EC=A7=80=EB=8F=84=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=20=EC=A0=9C=EC=99=B8)=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 29 +++++++++++++++++++ .../event/converter/EventConverter.java | 23 +++++++++++++++ .../domain/event/dto/EventResponseDTO.java | 28 ++++++++++++++++++ .../event/repository/EventRepository.java | 8 +++++ .../event/service/EventQueryService.java | 7 +++++ .../event/service/EventQueryServiceImpl.java | 28 ++++++++++++++++++ .../image/converter/ImageConverter.java | 16 ++++++++++ .../domain/image/dto/ImageResponseDTO.java | 20 +++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 4 +-- .../exception/handler/EventHandler.java | 10 +++++++ 10 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/event/controller/EventController.java create mode 100644 src/main/java/com/otakumap/domain/event/converter/EventConverter.java create mode 100644 src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/event/repository/EventRepository.java create mode 100644 src/main/java/com/otakumap/domain/event/service/EventQueryService.java create mode 100644 src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/image/converter/ImageConverter.java create mode 100644 src/main/java/com/otakumap/domain/image/dto/ImageResponseDTO.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java new file mode 100644 index 00000000..8bf189ca --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.event.controller; + +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.service.EventQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@Validated +public class EventController { + + private final EventQueryService eventQueryService; + + @Operation(summary = "이벤트 상세 정보 조회", description = "특정 이벤트의 상세 정보를 불러옵니다.") + @GetMapping("/events/{eventId}/details") + @Parameter(name = "eventId", description = "이벤트 Id") + public ApiResponse getEventDetail(@PathVariable Long eventId) { + return ApiResponse.onSuccess(eventQueryService.getEventDetail(eventId)); + } +} diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java new file mode 100644 index 00000000..618ff961 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -0,0 +1,23 @@ +package com.otakumap.domain.event.converter; + +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.dto.ImageResponseDTO; + +public class EventConverter { + + public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { + return EventResponseDTO.EventDetailDTO.builder() + .id(event.getId()) + .title(event.getTitle()) + .animationName(event.getAnimationName()) + .name(event.getName()) + .startDate(event.getStartDate()) + .endDate(event.getEndDate()) + .thumbnailImage(ImageConverter.toImageDTO(event.getThumbnailImage())) + .backgroundImage(ImageConverter.toImageDTO(event.getBackgroudImage())) + .goodsImage(ImageConverter.toImageDTO(event.getGoodsImage())) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java new file mode 100644 index 00000000..2065fb6d --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.event.dto; + +import com.otakumap.domain.image.dto.ImageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +public class EventResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventDetailDTO { + Long id; + String title; + String animationName; + String name; + LocalDate startDate; + LocalDate endDate; + ImageResponseDTO.ImageDTO thumbnailImage; + ImageResponseDTO.ImageDTO backgroundImage; + ImageResponseDTO.ImageDTO goodsImage; + } +} diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java new file mode 100644 index 00000000..96fd0d5e --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.event.repository; + +import com.otakumap.domain.event.entity.Event; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventRepository extends JpaRepository { + +} diff --git a/src/main/java/com/otakumap/domain/event/service/EventQueryService.java b/src/main/java/com/otakumap/domain/event/service/EventQueryService.java new file mode 100644 index 00000000..f38c69a2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/service/EventQueryService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event.service; + +import com.otakumap.domain.event.dto.EventResponseDTO; + +public interface EventQueryService { + EventResponseDTO.EventDetailDTO getEventDetail(Long eventId); +} diff --git a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java new file mode 100644 index 00000000..2a3afe2e --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.event.service; + +import com.otakumap.domain.event.converter.EventConverter; +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EventQueryServiceImpl implements EventQueryService{ + + private final EventRepository eventRepository; + + @Override + public EventResponseDTO.EventDetailDTO getEventDetail(Long eventId) { + Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + + return EventConverter.toEventDetailDTO(event); + } +} diff --git a/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java b/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java new file mode 100644 index 00000000..be2ef4fa --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.image.converter; + +import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.image.entity.Image; + +public class ImageConverter { + + public static ImageResponseDTO.ImageDTO toImageDTO(Image image) { + return ImageResponseDTO.ImageDTO.builder() + .id(image.getId()) + .uuid(image.getUuid()) + .fileName(image.getFileName()) + .fileUrl(image.getFileUrl()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/image/dto/ImageResponseDTO.java b/src/main/java/com/otakumap/domain/image/dto/ImageResponseDTO.java new file mode 100644 index 00000000..846dda20 --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/dto/ImageResponseDTO.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.image.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class ImageResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ImageDTO { + Long id; + String uuid; + String fileName; + String fileUrl; + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 2526a3d9..c3641899 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -16,8 +16,8 @@ public enum ErrorStatus implements BaseErrorCode { _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), - // 멤버 관려 에러 - MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."); + // 이벤트 관련 에러 + EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "존재하지 않는 이벤트입니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java new file mode 100644 index 00000000..fa631fb0 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/EventHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class EventHandler extends GeneralException { + public EventHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 601ba391963df35538771c13f4462475b59be573 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 10:20:12 +0900 Subject: [PATCH 017/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API?= =?UTF-8?q?=20=EC=A7=80=EB=8F=84=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../event/converter/EventConverter.java | 3 +++ .../domain/event/dto/EventResponseDTO.java | 3 +++ .../otakumap/domain/event/entity/Event.java | 5 ++++ .../converter/EventLocationConverter.java | 17 ++++++++++++++ .../dto/EventLocationResponseDTO.java | 23 +++++++++++++++++++ .../eventLocation/entity/EventLocation.java | 3 +-- 7 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java create mode 100644 src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java diff --git a/build.gradle b/build.gradle index ee11d8e6..3a585cf6 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' // swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 618ff961..06a45aef 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -2,6 +2,7 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.eventLocation.converter.EventLocationConverter; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.dto.ImageResponseDTO; @@ -13,11 +14,13 @@ public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { .title(event.getTitle()) .animationName(event.getAnimationName()) .name(event.getName()) + .site(event.getSite()) .startDate(event.getStartDate()) .endDate(event.getEndDate()) .thumbnailImage(ImageConverter.toImageDTO(event.getThumbnailImage())) .backgroundImage(ImageConverter.toImageDTO(event.getBackgroudImage())) .goodsImage(ImageConverter.toImageDTO(event.getGoodsImage())) + .eventLocation(EventLocationConverter.toEventLocationDTO(event.getEventLocation())) .build(); } } diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index 2065fb6d..4f381b75 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.event.dto; +import com.otakumap.domain.eventLocation.dto.EventLocationResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -19,10 +20,12 @@ public static class EventDetailDTO { String title; String animationName; String name; + String site; LocalDate startDate; LocalDate endDate; ImageResponseDTO.ImageDTO thumbnailImage; ImageResponseDTO.ImageDTO backgroundImage; ImageResponseDTO.ImageDTO goodsImage; + EventLocationResponseDTO.EventLocationDTO eventLocation; } } diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index cdbd69a5..80a288ec 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -3,6 +3,7 @@ import com.otakumap.domain.event.entity.enums.EventStatus; import com.otakumap.domain.event.entity.enums.EventType; import com.otakumap.domain.event.entity.enums.Genre; +import com.otakumap.domain.eventLocation.entity.EventLocation; import com.otakumap.domain.image.entity.Image; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -63,4 +64,8 @@ public class Event extends BaseEntity { @JoinColumn(name = "goods_image_id", referencedColumnName = "id") private Image goodsImage; + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "event_location_id") + private EventLocation eventLocation; + } diff --git a/src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java b/src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java new file mode 100644 index 00000000..b086abe8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java @@ -0,0 +1,17 @@ +package com.otakumap.domain.eventLocation.converter; + +import com.otakumap.domain.eventLocation.dto.EventLocationResponseDTO; +import com.otakumap.domain.eventLocation.entity.EventLocation; + +public class EventLocationConverter { + + public static EventLocationResponseDTO.EventLocationDTO toEventLocationDTO(EventLocation eventLocation) { + + return EventLocationResponseDTO.EventLocationDTO.builder() + .id(eventLocation.getId()) + .name(eventLocation.getName()) + .latitude(eventLocation.getLatitude()) + .longitude(eventLocation.getLongitude()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java b/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java new file mode 100644 index 00000000..98fd18f5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java @@ -0,0 +1,23 @@ +package com.otakumap.domain.eventLocation.dto; + +import com.otakumap.domain.image.dto.ImageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +public class EventLocationResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventLocationDTO { + Long id; + String name; + String longitude; + String latitude; + } +} diff --git a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java index f3c0d882..33b5edfe 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java +++ b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java @@ -26,7 +26,6 @@ public class EventLocation extends BaseEntity { @Column(columnDefinition = "TEXT") private String longitude; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "event_id", referencedColumnName = "id") + @OneToOne(mappedBy = "eventLocation", fetch = FetchType.LAZY) private Event event; } From 19a3769df672d6b930f25d3bb6e809481c93bdb3 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 11:02:51 +0900 Subject: [PATCH 018/516] =?UTF-8?q?Feat:=20User,=20EventShortReview,=20Use?= =?UTF-8?q?r=20=EA=B4=80=EB=A0=A8=20Enum=20=EC=B6=94=EA=B0=80=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/User/entity/User.java | 60 +++++++++++++++++++ .../com/otakumap/domain/User/enums/Role.java | 6 ++ .../domain/User/enums/SocialType.java | 7 +++ .../domain/User/enums/UserStatus.java | 6 ++ .../entity/EventShortReview.java | 51 ++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/User/entity/User.java create mode 100644 src/main/java/com/otakumap/domain/User/enums/Role.java create mode 100644 src/main/java/com/otakumap/domain/User/enums/SocialType.java create mode 100644 src/main/java/com/otakumap/domain/User/enums/UserStatus.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java diff --git a/src/main/java/com/otakumap/domain/User/entity/User.java b/src/main/java/com/otakumap/domain/User/entity/User.java new file mode 100644 index 00000000..eb7bbc5e --- /dev/null +++ b/src/main/java/com/otakumap/domain/User/entity/User.java @@ -0,0 +1,60 @@ +package com.otakumap.domain.User.entity; + +import com.otakumap.domain.User.enums.Role; +import com.otakumap.domain.User.enums.SocialType; +import com.otakumap.domain.User.enums.UserStatus; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "user") +public class User extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 20, nullable = false) + private String userId; + + @Column(length = 20, nullable = false) + private String password; + + @Column(length = 30, nullable = false) + private String email; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private SocialType socialType; + + @Column(length = 15, nullable = false) + private String name; + + @Column(length = 15) + private String nickname; + + @ColumnDefault("0") + @Column(nullable = false) + private Integer donation; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'ACTIVE'", nullable = false) + private UserStatus status; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'USER'", nullable = false) + private Role role; + + @ColumnDefault("FALSE") + @Column(name = "is_email_verified") + private Boolean isEmailVerified; +} diff --git a/src/main/java/com/otakumap/domain/User/enums/Role.java b/src/main/java/com/otakumap/domain/User/enums/Role.java new file mode 100644 index 00000000..78d488a4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/User/enums/Role.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.User.enums; + +public enum Role { + USER, + ADMIN +} diff --git a/src/main/java/com/otakumap/domain/User/enums/SocialType.java b/src/main/java/com/otakumap/domain/User/enums/SocialType.java new file mode 100644 index 00000000..695803ab --- /dev/null +++ b/src/main/java/com/otakumap/domain/User/enums/SocialType.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.User.enums; + +public enum SocialType { + KAKAO, + GOOGLE, + NAVER +} diff --git a/src/main/java/com/otakumap/domain/User/enums/UserStatus.java b/src/main/java/com/otakumap/domain/User/enums/UserStatus.java new file mode 100644 index 00000000..7903fa35 --- /dev/null +++ b/src/main/java/com/otakumap/domain/User/enums/UserStatus.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.User.enums; + +public enum UserStatus { + USER, + ADMIN +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java b/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java new file mode 100644 index 00000000..142d7c37 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java @@ -0,0 +1,51 @@ +package com.otakumap.domain.eventShortReview.entity; + +import com.otakumap.domain.User.entity.User; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventShortReview extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id") + private Event event; + + @Column(length = 50, nullable = false) + private String content; + + @Column(nullable = false) + private Float rating; + + @ColumnDefault("0") + private int likes; + + @ColumnDefault("0") + private int dislikes; + + + + + + + +} From 6574195e933e899c6eb06bcd610771b0ceb823c9 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 11:06:36 +0900 Subject: [PATCH 019/516] =?UTF-8?q?Feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/User/entity/User.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/otakumap/domain/User/entity/User.java b/src/main/java/com/otakumap/domain/User/entity/User.java index eb7bbc5e..0d52cf2b 100644 --- a/src/main/java/com/otakumap/domain/User/entity/User.java +++ b/src/main/java/com/otakumap/domain/User/entity/User.java @@ -3,6 +3,7 @@ import com.otakumap.domain.User.enums.Role; import com.otakumap.domain.User.enums.SocialType; import com.otakumap.domain.User.enums.UserStatus; +import com.otakumap.domain.image.entity.Image; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -32,6 +33,10 @@ public class User extends BaseEntity { @Column(length = 30, nullable = false) private String email; + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "profile_image_id", referencedColumnName = "id") + private Image profileImage; + @Enumerated(EnumType.STRING) @Column(columnDefinition = "VARCHAR(10)") private SocialType socialType; From 17b319cd549b3d7241047e9ff813a410afe36dae Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sat, 11 Jan 2025 13:20:37 +0900 Subject: [PATCH 020/516] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D,=20=EC=9D=B8?= =?UTF-8?q?=EA=B0=80=EB=A5=BC=20=EC=9C=84=ED=95=9C=20SecurityConfig=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ .../global/config/SecurityConfig.java | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/com/otakumap/global/config/SecurityConfig.java diff --git a/build.gradle b/build.gradle index ee11d8e6..b4324006 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,9 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + + // security 관련 + implementation 'org.springframework.boot:spring-boot-starter-security' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java new file mode 100644 index 00000000..49b3667e --- /dev/null +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -0,0 +1,50 @@ +package com.otakumap.global.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + + // 현재 oauth와 jwt 설정 안 해놔서 작동 안 하기 때문에 해당 코드는 주석처리함 +// private final JwtAuthenticationFilter jwtAuthenticationFilter; +// private final AuthenticationProvider authenticationProvider; +// private final CustomOAuth2UserService oAuth2UserService; +// private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll() + .requestMatchers("/", "/home", "/signup", "/members/signup", "/css/**").permitAll() + .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) +// .authenticationProvider(authenticationProvider) +// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) +// .oauth2Login(oauth2 -> oauth2 +// .loginPage("/login") +// .userInfoEndpoint(userInfo -> userInfo.userService(oAuth2UserService)) +// .successHandler(oAuth2LoginSuccessHandler) +// ) + ; + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} From a4d7644a6eecc4162276dc37bc151226466c7f89 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 13:24:13 +0900 Subject: [PATCH 021/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=ED=9B=84=EA=B8=B0=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/User/enums/Role.java | 1 + .../domain/User/enums/UserStatus.java | 5 ++- .../User/repository/UserRepository.java | 7 ++++ .../EventShortReviewController.java | 34 +++++++++++++++ .../converter/EventShortReviewConverter.java | 32 +++++++++++++++ .../dto/EventShortReviewRequestDTO.java | 16 ++++++++ .../dto/EventShortReviewResponseDTO.java | 26 ++++++++++++ .../entity/EventShortReview.java | 6 --- .../EventShortReviewRepository.java | 7 ++++ .../EventShortReviewCommandService.java | 8 ++++ .../EventShortReviewCommandServiceImpl.java | 41 +++++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 7 +++- .../exception/handler/UserHandler.java | 10 +++++ 13 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/User/repository/UserRepository.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java create mode 100644 src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java diff --git a/src/main/java/com/otakumap/domain/User/enums/Role.java b/src/main/java/com/otakumap/domain/User/enums/Role.java index 78d488a4..63c99dea 100644 --- a/src/main/java/com/otakumap/domain/User/enums/Role.java +++ b/src/main/java/com/otakumap/domain/User/enums/Role.java @@ -3,4 +3,5 @@ public enum Role { USER, ADMIN + } diff --git a/src/main/java/com/otakumap/domain/User/enums/UserStatus.java b/src/main/java/com/otakumap/domain/User/enums/UserStatus.java index 7903fa35..64edb792 100644 --- a/src/main/java/com/otakumap/domain/User/enums/UserStatus.java +++ b/src/main/java/com/otakumap/domain/User/enums/UserStatus.java @@ -1,6 +1,7 @@ package com.otakumap.domain.User.enums; public enum UserStatus { - USER, - ADMIN + ACTIVE, + INACTIVE + } diff --git a/src/main/java/com/otakumap/domain/User/repository/UserRepository.java b/src/main/java/com/otakumap/domain/User/repository/UserRepository.java new file mode 100644 index 00000000..f5cbbdca --- /dev/null +++ b/src/main/java/com/otakumap/domain/User/repository/UserRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.User.repository; + +import com.otakumap.domain.User.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java new file mode 100644 index 00000000..5bd4b75c --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java @@ -0,0 +1,34 @@ +package com.otakumap.domain.eventShortReview.controller; + +import com.otakumap.domain.eventShortReview.converter.EventShortReviewConverter; +import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.eventShortReview.dto.EventShortReviewResponseDTO; +import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.eventShortReview.service.EventShortReviewCommandService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@Validated +public class EventShortReviewController { + + private final EventShortReviewCommandService eventShortReviewCommandService; + + @Operation(summary = "이벤트 한 줄 리뷰 작성", description = "이벤트에 한 줄 리뷰를 작성합니다.") + @PostMapping("/events/{eventId}/short-reviews") + @Parameters({ + @Parameter(name = "eventId", description = "특정 이벤트의 Id") + }) + public ApiResponse createEventShortReview(@PathVariable Long eventId, + @RequestBody EventShortReviewRequestDTO.NewEventShortReviewDTO request) { + EventShortReview eventShortReview = eventShortReviewCommandService.createEventShortReview(eventId, request); + return ApiResponse.onSuccess(EventShortReviewConverter.toNewEventShortReviewDTO(eventShortReview)); + } +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java b/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java new file mode 100644 index 00000000..312aefe4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java @@ -0,0 +1,32 @@ +package com.otakumap.domain.eventShortReview.converter; + +import com.otakumap.domain.User.entity.User; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.eventShortReview.dto.EventShortReviewResponseDTO; +import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.image.converter.ImageConverter; + +import java.time.LocalDateTime; + +public class EventShortReviewConverter { + public static EventShortReview toEventShortReview(EventShortReviewRequestDTO.NewEventShortReviewDTO request, Event event, User user) { + return EventShortReview.builder() + .event(event) + .user(user) + .rating(request.getRating()) + .content(request.getContent()) + .build(); + } + + public static EventShortReviewResponseDTO.NewEventShortReviewDTO toNewEventShortReviewDTO(EventShortReview eventShortReview) { + return EventShortReviewResponseDTO.NewEventShortReviewDTO.builder() + .userId(eventShortReview.getUser().getId()) + .userName(eventShortReview.getUser().getName()) + .eventId(eventShortReview.getEvent().getId()) + .content(eventShortReview.getContent()) + .rating(eventShortReview.getRating()) + .profileImage(ImageConverter.toImageDTO(eventShortReview.getUser().getProfileImage())) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java new file mode 100644 index 00000000..3ae250af --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.eventShortReview.dto; + +import com.otakumap.domain.User.entity.User; +import lombok.Getter; +import org.springframework.data.jpa.repository.JpaRepository; + +public class EventShortReviewRequestDTO { + + @Getter + public static class NewEventShortReviewDTO { + Long userId; // 로그인 구현 전까지 임의로 request body로 받음 + Float rating; + String content; + + } +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java new file mode 100644 index 00000000..630afa25 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java @@ -0,0 +1,26 @@ +package com.otakumap.domain.eventShortReview.dto; + +import com.otakumap.domain.User.entity.User; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class EventShortReviewResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class NewEventShortReviewDTO { + Long userId; + Long eventId; + String content; + Float rating; + String userName; + ImageResponseDTO.ImageDTO profileImage; + int likes; + int dislikes; + } +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java b/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java index 142d7c37..2321a83e 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java @@ -42,10 +42,4 @@ public class EventShortReview extends BaseEntity { @ColumnDefault("0") private int dislikes; - - - - - - } diff --git a/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java b/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java new file mode 100644 index 00000000..3d2fece1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.eventShortReview.repository; + +import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventShortReviewRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java new file mode 100644 index 00000000..0aa4904e --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.eventShortReview.service; + +import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.eventShortReview.entity.EventShortReview; + +public interface EventShortReviewCommandService { + EventShortReview createEventShortReview(Long eventId, EventShortReviewRequestDTO.NewEventShortReviewDTO request); +} diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java new file mode 100644 index 00000000..cad08ad6 --- /dev/null +++ b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java @@ -0,0 +1,41 @@ +package com.otakumap.domain.eventShortReview.service; + +import com.otakumap.domain.User.entity.User; +import com.otakumap.domain.User.repository.UserRepository; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.eventShortReview.converter.EventShortReviewConverter; +import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.eventShortReview.repository.EventShortReviewRepository; +import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class EventShortReviewCommandServiceImpl implements EventShortReviewCommandService{ + private final UserRepository userRepository; + private final EventShortReviewRepository eventShortReviewRepository; + private final EventRepository eventRepository; + + @Override + @Transactional + public EventShortReview createEventShortReview(Long eventId, EventShortReviewRequestDTO.NewEventShortReviewDTO request) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + + User user = userRepository.findById(request.getUserId()) + .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + + EventShortReview eventShortReview = EventShortReviewConverter.toEventShortReview(request, event, user); + + return eventShortReviewRepository.save(eventShortReview); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index c3641899..3d636372 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -16,8 +16,11 @@ public enum ErrorStatus implements BaseErrorCode { _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), - // 이벤트 관련 에러 - EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "존재하지 않는 이벤트입니다."); + // 멤버 관련 에러 + USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), + + // 이벤트 상세 정보 관련 에러 + EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java new file mode 100644 index 00000000..f5e85062 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/UserHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class UserHandler extends GeneralException { + public UserHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 13b8151f3b2b6d9fe786e493fdc6f7fdee90ef27 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 11 Jan 2025 13:32:28 +0900 Subject: [PATCH 022/516] =?UTF-8?q?Chore:=20=EC=95=88=20=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20import=EB=AC=B8=20=EC=A0=95=EB=A6=AC=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/event/converter/EventConverter.java | 1 - .../domain/eventLocation/dto/EventLocationResponseDTO.java | 3 --- .../otakumap/domain/eventLocation/entity/EventLocation.java | 1 - .../eventShortReview/converter/EventShortReviewConverter.java | 2 -- .../eventShortReview/dto/EventShortReviewRequestDTO.java | 2 -- .../eventShortReview/dto/EventShortReviewResponseDTO.java | 1 - .../service/EventShortReviewCommandServiceImpl.java | 2 -- 7 files changed, 12 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 06a45aef..ac292223 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -4,7 +4,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.eventLocation.converter.EventLocationConverter; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.image.dto.ImageResponseDTO; public class EventConverter { diff --git a/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java b/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java index 98fd18f5..ae177d6a 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java @@ -1,13 +1,10 @@ package com.otakumap.domain.eventLocation.dto; -import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; - public class EventLocationResponseDTO { @Builder diff --git a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java index 33b5edfe..0d6bc35e 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java +++ b/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java @@ -1,7 +1,6 @@ package com.otakumap.domain.eventLocation.entity; import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.event.entity.enums.EventStatus; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java b/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java index 312aefe4..bc396169 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java @@ -7,8 +7,6 @@ import com.otakumap.domain.eventShortReview.entity.EventShortReview; import com.otakumap.domain.image.converter.ImageConverter; -import java.time.LocalDateTime; - public class EventShortReviewConverter { public static EventShortReview toEventShortReview(EventShortReviewRequestDTO.NewEventShortReviewDTO request, Event event, User user) { return EventShortReview.builder() diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java index 3ae250af..939ea944 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java @@ -1,8 +1,6 @@ package com.otakumap.domain.eventShortReview.dto; -import com.otakumap.domain.User.entity.User; import lombok.Getter; -import org.springframework.data.jpa.repository.JpaRepository; public class EventShortReviewRequestDTO { diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java index 630afa25..133b3286 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java @@ -1,6 +1,5 @@ package com.otakumap.domain.eventShortReview.dto; -import com.otakumap.domain.User.entity.User; import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java index cad08ad6..63df1371 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java @@ -8,8 +8,6 @@ import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; import com.otakumap.domain.eventShortReview.entity.EventShortReview; import com.otakumap.domain.eventShortReview.repository.EventShortReviewRepository; -import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.image.dto.ImageResponseDTO; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; From cb90b8192dc5df882cac60c0dddd5a6659ca55bc Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 11 Jan 2025 16:13:12 +0900 Subject: [PATCH 023/516] =?UTF-8?q?Feat:=20=EB=AA=85=EC=86=8C=20=ED=95=9C?= =?UTF-8?q?=20=EC=A4=84=20=ED=9B=84=EA=B8=B0=20=EC=9D=BD=EA=B8=B0(R)=20API?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/entity/Place.java | 30 ++++++++++++ .../place/repository/PlaceRepository.java | 7 +++ .../DTO/PlaceShortReviewResponseDTO.java | 49 +++++++++++++++++++ .../PlaceShortReviewController.java | 35 +++++++++++++ .../converter/PlaceShortReviewConverter.java | 46 +++++++++++++++++ .../entity/PlaceShortReview.java | 38 ++++++++++++++ .../PlaceShortReviewRepository.java | 11 +++++ .../service/PlaceShortReviewQueryService.java | 8 +++ .../PlaceShortReviewQueryServiceImpl.java | 24 +++++++++ 9 files changed, 248 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/place/entity/Place.java create mode 100644 src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java new file mode 100644 index 00000000..9d916a46 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.place.entity; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Place extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String name; + + @Column(nullable = false, length = 100) + private String detail; + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List reviews = new ArrayList<>(); +} diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java new file mode 100644 index 00000000..9807a873 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place.repository; + +import com.otakumap.domain.place.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java new file mode 100644 index 00000000..657ac46a --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java @@ -0,0 +1,49 @@ +package com.otakumap.domain.place_short_review.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class PlaceShortReviewResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceShortReviewListDTO { + Long placeId; + String placeName; + Integer currentPage; + Integer totalPages; + List shortReviews; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceShortReviewDTO { + Long id; + PlaceShortReviewUserDTO user; + String content; + Float rating; + LocalDateTime createdAt; + Long likes; + Long dislikes; + } + + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceShortReviewUserDTO { + Long id; + String nickname; + // ImageResponseDTO.ImageDTO profileImage; + } +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java new file mode 100644 index 00000000..be1e0e55 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -0,0 +1,35 @@ +package com.otakumap.domain.place_short_review.controller; + +import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewResponseDTO; +import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; +import com.otakumap.domain.place_short_review.service.PlaceShortReviewQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +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.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/places") +public class PlaceShortReviewController { + + + private final PlaceShortReviewQueryService placeShortReviewQueryService; + + @GetMapping("/{placeId}/short-review") + @Operation(summary = "특정 명소의 한 줄 리뷰 목록 조회 API", description = "특정 명소의 한 줄 리뷰 목록을 조회하는 API이며, 페이징을 포함합니다. query string으로 page 번호를 함께 보내주세요.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "placeId", description = "명소의 아이디입니다.") + }) + public ApiResponse getPlaceShortReviewList(@PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ + return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); + } +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java new file mode 100644 index 00000000..e66cc14e --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -0,0 +1,46 @@ +package com.otakumap.domain.place_short_review.converter; + +import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewResponseDTO; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + +public class PlaceShortReviewConverter { + public static PlaceShortReviewResponseDTO.PlaceShortReviewDTO placeShortReviewDTO(PlaceShortReview placeShortReview) { + PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO user = PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO.builder() + .id(placeShortReview.getUser().getId()) + .nickname(placeShortReview.getUser().getNickname()) + //.profilePicture(placeShortReview.getUser().getProfilePicture() + .build(); + + return PlaceShortReviewResponseDTO.PlaceShortReviewDTO.builder() + .id(placeShortReview.getId()) + .user(user) + .content(placeShortReview.getContent()) + .rating(placeShortReview.getRating()) + .createdAt(placeShortReview.getCreatedAt()) + .likes(placeShortReview.getLikes()) + .dislikes(placeShortReview.getDislikes()) + .build(); + } + + public static PlaceShortReviewResponseDTO.PlaceShortReviewListDTO placeShortReviewListDTO(Page reviewList) { + + if(reviewList == null || reviewList.isEmpty()) return null; + + List placeShortReviewDTOList = reviewList.stream() + .map(PlaceShortReviewConverter::placeShortReviewDTO).collect(Collectors.toList()); + + PlaceShortReview review = reviewList.getContent().get(0); + + return PlaceShortReviewResponseDTO.PlaceShortReviewListDTO.builder() + .placeId(review.getPlace().getId()) + .placeName(review.getPlace().getName()) + .currentPage(reviewList.getNumber() + 1) + .totalPages(reviewList.getTotalPages()) + .shortReviews(placeShortReviewDTOList) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java new file mode 100644 index 00000000..cf5dc001 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.place_short_review.entity; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceShortReview extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String content; + + @Column(nullable = false) + private float rating; + + @Column(columnDefinition = "bigint default 0 not null") + private Long likes; + + @Column(columnDefinition = "bigint default 0 not null") + private Long dislikes; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java new file mode 100644 index 00000000..71f6ccda --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.place_short_review.repository; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceShortReviewRepository extends JpaRepository { + Page findAllByPlace(Place place, PageRequest pageRequest); +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java new file mode 100644 index 00000000..d13157c7 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.place_short_review.service; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import org.springframework.data.domain.Page; + +public interface PlaceShortReviewQueryService { + Page getPlaceShortReviews(Long placeId, Integer page); +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java new file mode 100644 index 00000000..396732c4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java @@ -0,0 +1,24 @@ +package com.otakumap.domain.place_short_review.service; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PlaceShortReviewQueryServiceImpl implements PlaceShortReviewQueryService { + + private final PlaceRepository placeRepository; + private final PlaceShortReviewRepository placeShortReviewRepository; + + @Override + public Page getPlaceShortReviews(Long placeId, Integer page) { + Place place = placeRepository.findById(placeId).get(); + return placeShortReviewRepository.findAllByPlace(place, PageRequest.of(page, 6)); + } +} From b386c0ad320f88a61e03179d2252136336bcb152 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 11 Jan 2025 16:59:07 +0900 Subject: [PATCH 024/516] =?UTF-8?q?Feat:=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AA=85=EC=86=8C=20validatio?= =?UTF-8?q?n=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlaceShortReviewController.java | 3 +- .../PlaceShortReviewQueryServiceImpl.java | 4 ++- .../apiPayload/code/status/ErrorStatus.java | 3 ++ .../validation/annotation/ExistPlace.java | 18 +++++++++++ .../validator/PlaceExistValidator.java | 32 +++++++++++++++++++ 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index be1e0e55..71609867 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -4,6 +4,7 @@ import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; import com.otakumap.domain.place_short_review.service.PlaceShortReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ExistPlace; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -29,7 +30,7 @@ public class PlaceShortReviewController { @Parameters({ @Parameter(name = "placeId", description = "명소의 아이디입니다.") }) - public ApiResponse getPlaceShortReviewList(@PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ + public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java index 396732c4..73879404 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java @@ -4,6 +4,8 @@ import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -18,7 +20,7 @@ public class PlaceShortReviewQueryServiceImpl implements PlaceShortReviewQuerySe @Override public Page getPlaceShortReviews(Long placeId, Integer page) { - Place place = placeRepository.findById(placeId).get(); + Place place = placeRepository.findById(placeId).orElseThrow(() -> new UserHandler(ErrorStatus.PLACE_NOT_FOUND)); return placeShortReviewRepository.findAllByPlace(place, PageRequest.of(page, 6)); } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index eefd2038..c929fbb3 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -19,6 +19,9 @@ public enum ErrorStatus implements BaseErrorCode { // 멤버 관련 에러 USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), + // 명소 관련 에러 + PLACE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4001", "존재하지 않는 명소입니다."), + // 이벤트 좋아요 관련 에러 EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."); diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java b/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java new file mode 100644 index 00000000..6827b9d6 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java @@ -0,0 +1,18 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.PlaceExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = PlaceExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistPlace { + + String message() default "해당하는 명소가 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java new file mode 100644 index 00000000..e6b6129b --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java @@ -0,0 +1,32 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistPlace; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PlaceExistValidator implements ConstraintValidator { + + private final PlaceRepository placeRepository; + + @Override + public void initialize(ExistPlace constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long placeId, ConstraintValidatorContext context) { + boolean isValid = placeRepository.existsById(placeId); + + if(!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.PLACE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} From 9322870fee0b09ed361bb01316087ed52d463249 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 11 Jan 2025 23:20:25 +0900 Subject: [PATCH 025/516] =?UTF-8?q?Chore:=20api=20endpoint=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 71609867..7a2e818f 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -16,13 +16,13 @@ @RestController @RequiredArgsConstructor @Validated -@RequestMapping("/places") +@RequestMapping("/api") public class PlaceShortReviewController { private final PlaceShortReviewQueryService placeShortReviewQueryService; - @GetMapping("/{placeId}/short-review") + @GetMapping("/places/{placeId}/short-review") @Operation(summary = "특정 명소의 한 줄 리뷰 목록 조회 API", description = "특정 명소의 한 줄 리뷰 목록을 조회하는 API이며, 페이징을 포함합니다. query string으로 page 번호를 함께 보내주세요.") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), From d5465d1bb45771257e383abc3f613289f85f931a Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 12 Jan 2025 00:08:42 +0900 Subject: [PATCH 026/516] =?UTF-8?q?Feat:=20EventStatus=EC=97=90=20ENEDED?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/event/entity/enums/EventStatus.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java b/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java index 36415e0a..ca809874 100644 --- a/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java +++ b/src/main/java/com/otakumap/domain/event/entity/enums/EventStatus.java @@ -2,5 +2,7 @@ public enum EventStatus { NOT_STARTED, - IN_PROCESS + IN_PROCESS, + + ENDED, } From 03fbebafd439c276837ca2214098fa4b074bff33 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 12 Jan 2025 00:26:19 +0900 Subject: [PATCH 027/516] =?UTF-8?q?Feat:=20Event=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=88=98=EC=A0=95=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/event/entity/Event.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index f3b5dce8..a5c9350f 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -3,16 +3,14 @@ import com.otakumap.domain.event.entity.enums.EventStatus; import com.otakumap.domain.event.entity.enums.EventType; import com.otakumap.domain.event.entity.enums.Genre; - import com.otakumap.domain.eventLocation.entity.EventLocation; +import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.image.entity.Image; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDate; - -import com.otakumap.domain.event_like.entity.EventLike; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; From f52315fcd3c8f3e60ca002fb7f04fdc540f1627c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 03:48:01 +0900 Subject: [PATCH 028/516] =?UTF-8?q?Feat:=20=ED=95=9C=20=EC=A4=84=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=9E=91=EC=84=B1=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/entity/Place.java | 30 +++++++++++++ .../place/repository/PlaceRepository.java | 7 +++ .../PlaceShortReviewController.java | 27 +++++++++++ .../converter/PlaceShortReviewConverter.java | 29 ++++++++++++ .../dto/PlaceShortReviewRequestDTO.java | 16 +++++++ .../dto/PlaceShortReviewResponseDTO.java | 24 ++++++++++ .../entity/PlaceShortReview.java | 45 +++++++++++++++++++ .../PlaceShortReviewRepository.java | 8 ++++ .../PlaceShortReviewCommandService.java | 8 ++++ .../PlaceShortReviewCommandServiceImpl.java | 38 ++++++++++++++++ .../service/PlaceShortReviewQueryService.java | 4 ++ .../PlaceShortReviewQueryServiceImpl.java | 4 ++ .../apiPayload/code/status/ErrorStatus.java | 5 ++- .../exception/handler/PlaceHandler.java | 10 +++++ 14 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/place/entity/Place.java create mode 100644 src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java new file mode 100644 index 00000000..9d916a46 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.place.entity; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Place extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String name; + + @Column(nullable = false, length = 100) + private String detail; + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List reviews = new ArrayList<>(); +} diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java new file mode 100644 index 00000000..9807a873 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place.repository; + +import com.otakumap.domain.place.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java new file mode 100644 index 00000000..a474f0e5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.place_short_review.controller; + +import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewResponseDTO; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.service.PlaceShortReviewCommandService; +import com.otakumap.global.apiPayload.ApiResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/places") +public class PlaceShortReviewController { + private final PlaceShortReviewCommandService placeShortReviewCommandService; + + @PostMapping("/{placeId}/short-review") + public ApiResponse createReview( + @PathVariable Long placeId, + // TODO: place id validation + @RequestBody @Valid PlaceShortReviewRequestDTO.CreateDTO request) { + PlaceShortReview placeShortReview = placeShortReviewCommandService.createReview(placeId, request); + return ApiResponse.onSuccess(PlaceShortReviewConverter.toCreateReviewDTO(placeShortReview)); + } +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java new file mode 100644 index 00000000..1a7edac1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.place_short_review.converter; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewResponseDTO; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.user.entity.User; + +public class PlaceShortReviewConverter { + public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(PlaceShortReview placeShortReview) { + User user = placeShortReview.getUser(); + return PlaceShortReviewResponseDTO.CreateReviewDTO.builder() + .reviewId(placeShortReview.getId()) + .rating(placeShortReview.getRating().intValue()) + .content(placeShortReview.getContent()) + .createdAt(placeShortReview.getCreatedAt()) + .userId(user.getId()) + .nickname(user.getNickname()) + .build(); + } + + public static PlaceShortReview toPlaceShortReview(PlaceShortReviewRequestDTO.CreateDTO request, User user) { + return PlaceShortReview.builder() + .user(user) + .rating(request.getRating()) + .content(request.getContent()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java new file mode 100644 index 00000000..c8042e33 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.place_short_review.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class PlaceShortReviewRequestDTO { + @Getter + public static class CreateDTO { + Long userId; // 토큰 사용 전 임시 + @NotNull + Float rating; + @NotBlank + String content; + } +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java new file mode 100644 index 00000000..252c46bb --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -0,0 +1,24 @@ +package com.otakumap.domain.place_short_review.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class PlaceShortReviewResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CreateReviewDTO { + private Long reviewId; + private Integer rating; + private String content; + private LocalDateTime createdAt; + private Long userId; + private String nickname; +// private String profilePicture; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java new file mode 100644 index 00000000..bb725b25 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java @@ -0,0 +1,45 @@ +package com.otakumap.domain.place_short_review.entity; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceShortReview extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String content; + + @Column(nullable = false) + private Float rating; + + @Column(nullable = false) + @ColumnDefault("0") + private Long likes; + + @Column(nullable = false) + @ColumnDefault("0") + private Long dislikes; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java new file mode 100644 index 00000000..e06cac8e --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.place_short_review.repository; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceShortReviewRepository extends JpaRepository { + +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java new file mode 100644 index 00000000..ce21b829 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.place_short_review.service; + +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; + +public interface PlaceShortReviewCommandService { + PlaceShortReview createReview(Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java new file mode 100644 index 00000000..e6a1bbaa --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.place_short_review.service; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PlaceShortReviewCommandServiceImpl implements PlaceShortReviewCommandService { + private final PlaceShortReviewRepository placeShortReviewRepository; + private final UserRepository userRepository; + private final PlaceRepository placeRepository; + + @Override + @Transactional + public PlaceShortReview createReview(Long placeId, PlaceShortReviewRequestDTO.CreateDTO request) { + User user = userRepository.findById(request.getUserId()) + .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + PlaceShortReview newReview = PlaceShortReviewConverter.toPlaceShortReview(request, user); + + return placeShortReviewRepository.save(newReview); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java new file mode 100644 index 00000000..18c9fff8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java @@ -0,0 +1,4 @@ +package com.otakumap.domain.place_short_review.service; + +public interface PlaceShortReviewQueryService { +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java new file mode 100644 index 00000000..706d6bf2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java @@ -0,0 +1,4 @@ +package com.otakumap.domain.place_short_review.service; + +public class PlaceShortReviewQueryServiceImpl { +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index eefd2038..26a44f9d 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -20,7 +20,10 @@ public enum ErrorStatus implements BaseErrorCode { USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), // 이벤트 좋아요 관련 에러 - EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."); + EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), + + // 장소 관련 에러 + PLACE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4001", "장소가 없습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java new file mode 100644 index 00000000..aba750f9 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class PlaceHandler extends GeneralException { + public PlaceHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 0d5e73d402999f3455347eae6daa332d109a0ab0 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 03:53:36 +0900 Subject: [PATCH 029/516] =?UTF-8?q?Fix:=20db=EC=97=90=20place=20id?= =?UTF-8?q?=EA=B0=80=20null=EB=A1=9C=20=EC=A0=80=EC=9E=A5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceShortReviewConverter.java | 3 ++- .../place_short_review/dto/PlaceShortReviewResponseDTO.java | 1 + .../service/PlaceShortReviewCommandServiceImpl.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index 1a7edac1..63cca85b 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -19,9 +19,10 @@ public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(Plac .build(); } - public static PlaceShortReview toPlaceShortReview(PlaceShortReviewRequestDTO.CreateDTO request, User user) { + public static PlaceShortReview toPlaceShortReview(PlaceShortReviewRequestDTO.CreateDTO request, User user, Place place) { return PlaceShortReview.builder() .user(user) + .place(place) .rating(request.getRating()) .content(request.getContent()) .build(); diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java index 252c46bb..29b96b03 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -14,6 +14,7 @@ public class PlaceShortReviewResponseDTO { @AllArgsConstructor public static class CreateReviewDTO { private Long reviewId; + private Long placeId; private Integer rating; private String content; private LocalDateTime createdAt; diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index e6a1bbaa..ac6ebf57 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -31,7 +31,7 @@ public PlaceShortReview createReview(Long placeId, PlaceShortReviewRequestDTO.Cr Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - PlaceShortReview newReview = PlaceShortReviewConverter.toPlaceShortReview(request, user); + PlaceShortReview newReview = PlaceShortReviewConverter.toPlaceShortReview(request, user, place); return placeShortReviewRepository.save(newReview); } From 8f79c79a024482929045c3cdf76e37efebbc4a28 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 03:58:45 +0900 Subject: [PATCH 030/516] =?UTF-8?q?Fix:=20response=EC=97=90=EC=84=9C=20pla?= =?UTF-8?q?ceId=EA=B0=80=20null=EB=A1=9C=20=ED=91=9C=EC=8B=9C=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_short_review/converter/PlaceShortReviewConverter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index 63cca85b..6b30b5bd 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -9,6 +9,7 @@ public class PlaceShortReviewConverter { public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(PlaceShortReview placeShortReview) { User user = placeShortReview.getUser(); + Place place = placeShortReview.getPlace(); return PlaceShortReviewResponseDTO.CreateReviewDTO.builder() .reviewId(placeShortReview.getId()) .rating(placeShortReview.getRating().intValue()) @@ -16,6 +17,7 @@ public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(Plac .createdAt(placeShortReview.getCreatedAt()) .userId(user.getId()) .nickname(user.getNickname()) + .placeId(place.getId()) .build(); } From 38741639b805493fd0b2594075bdbd3426d60cef Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 04:10:06 +0900 Subject: [PATCH 031/516] =?UTF-8?q?Fix:=20rating=EC=9D=84=20Float=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_short_review/converter/PlaceShortReviewConverter.java | 2 +- .../place_short_review/dto/PlaceShortReviewResponseDTO.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index 6b30b5bd..1ab8f45d 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -12,7 +12,7 @@ public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(Plac Place place = placeShortReview.getPlace(); return PlaceShortReviewResponseDTO.CreateReviewDTO.builder() .reviewId(placeShortReview.getId()) - .rating(placeShortReview.getRating().intValue()) + .rating(placeShortReview.getRating()) .content(placeShortReview.getContent()) .createdAt(placeShortReview.getCreatedAt()) .userId(user.getId()) diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java index 29b96b03..c43cf18a 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -15,7 +15,7 @@ public class PlaceShortReviewResponseDTO { public static class CreateReviewDTO { private Long reviewId; private Long placeId; - private Integer rating; + private Float rating; private String content; private LocalDateTime createdAt; private Long userId; From 4d711650c3190de0c3b1816393bc20ebf3647a8f Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 04:29:32 +0900 Subject: [PATCH 032/516] =?UTF-8?q?Refactor:=20response=20dto=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 1 - .../converter/PlaceShortReviewConverter.java | 1 - .../place_short_review/dto/PlaceShortReviewResponseDTO.java | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index a474f0e5..83a08a27 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -19,7 +19,6 @@ public class PlaceShortReviewController { @PostMapping("/{placeId}/short-review") public ApiResponse createReview( @PathVariable Long placeId, - // TODO: place id validation @RequestBody @Valid PlaceShortReviewRequestDTO.CreateDTO request) { PlaceShortReview placeShortReview = placeShortReviewCommandService.createReview(placeId, request); return ApiResponse.onSuccess(PlaceShortReviewConverter.toCreateReviewDTO(placeShortReview)); diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index 1ab8f45d..1749d3cd 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -16,7 +16,6 @@ public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(Plac .content(placeShortReview.getContent()) .createdAt(placeShortReview.getCreatedAt()) .userId(user.getId()) - .nickname(user.getNickname()) .placeId(place.getId()) .build(); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java index c43cf18a..7edbc443 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -14,12 +14,10 @@ public class PlaceShortReviewResponseDTO { @AllArgsConstructor public static class CreateReviewDTO { private Long reviewId; - private Long placeId; private Float rating; private String content; private LocalDateTime createdAt; private Long userId; - private String nickname; -// private String profilePicture; + private Long placeId; } } \ No newline at end of file From 6fb2b3d4f134280e8220407bdf89ab0543495305 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sun, 12 Jan 2025 09:38:42 +0900 Subject: [PATCH 033/516] =?UTF-8?q?[Feat]=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceLikeController.java | 46 +++++++++++++ .../converter/PlaceLikeConverter.java | 25 +++++++ .../place_like/dto/PlaceLikeResponseDTO.java | 32 +++++++++ .../domain/place_like/entity/PlaceLike.java | 38 +++++++++++ .../repository/PlaceLikeRepository.java | 14 ++++ .../service/PlaceLikeCommandService.java | 10 +++ .../service/PlaceLikeCommandServiceImpl.java | 24 +++++++ .../service/PlaceLikeQueryService.java | 8 +++ .../service/PlaceLikeQueryServiceImpl.java | 66 +++++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 5 +- .../exception/handler/PlaceHandler.java | 10 +++ .../validation/annotation/ExistPlaceLike.java | 17 +++++ .../validator/PlaceLikeExistValidator.java | 38 +++++++++++ 13 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java create mode 100644 src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java create mode 100644 src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java create mode 100644 src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java create mode 100644 src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java new file mode 100644 index 00000000..7222b54e --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -0,0 +1,46 @@ +package com.otakumap.domain.place_like.controller; + +import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.service.PlaceLikeCommandService; +import com.otakumap.domain.place_like.service.PlaceLikeQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ExistPlaceLike; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@Validated +public class PlaceLikeController { + private final PlaceLikeQueryService placeLikeQueryService; + private final PlaceLikeCommandService placeLikeCommandService; + + // 로그인 시 엔드포인트 변경 예정 + @Operation(summary = "저장된 장소 목록 조회", description = "저장된 장소 목록을 불러옵니다.") + @GetMapping( "/users/{userId}/saved-places") + @Parameters({ + @Parameter(name = "userId", description = "사용자 ID"), + @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), + @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") + }) + public ApiResponse getPlaceLikeList(@PathVariable Long userId, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(userId, lastId, limit)); + } + + @Operation(summary = "저장된 장소 삭제", description = "저장된 장소를 삭제합니다.") + @DeleteMapping("/saved-places") + @Parameters({ + @Parameter(name = "placeIds", description = "저장된 장소 id List"), + }) + public ApiResponse deletePlaceLike(@RequestParam(required = false) @ExistPlaceLike List placeIds) { + placeLikeCommandService.deletePlaceLike(placeIds); + return ApiResponse.onSuccess("저장된 장소가 성공적으로 삭제되었습니다"); + } +} diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java new file mode 100644 index 00000000..3d75fe06 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -0,0 +1,25 @@ +package com.otakumap.domain.place_like.converter; + +import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.entity.PlaceLike; + +import java.util.List; + +public class PlaceLikeConverter { + public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(PlaceLike placeLike) { + return PlaceLikeResponseDTO.PlaceLikePreViewDTO.builder() + .id(placeLike.getId()) + .userId(placeLike.getUser().getId()) + .placeId(placeLike.getPlace().getId()) + .isFavorite(placeLike.getIsFavorite()) + .build(); + + } + public static PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikePreViewListDTO(List placeLikes, boolean hasNext, Long lastId) { + return PlaceLikeResponseDTO.PlaceLikePreViewListDTO.builder() + .placeLikes(placeLikes) + .hasNext(hasNext) + .lastId(lastId) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java new file mode 100644 index 00000000..452540a5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -0,0 +1,32 @@ +package com.otakumap.domain.place_like.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +public class PlaceLikeResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceLikePreViewDTO { + Long id; + Long userId; + Long placeId; + Boolean isFavorite; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceLikePreViewListDTO { + List placeLikes; + boolean hasNext; + Long lastId; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java new file mode 100644 index 00000000..487c1b87 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.place_like.entity; + + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceLike extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "place_id", nullable = false) + private Place place; + + @Column(name = "is_favorite", nullable = false) + @ColumnDefault("false") + private Boolean isFavorite; + +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java new file mode 100644 index 00000000..5594df93 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.place_like.repository; + +import com.otakumap.domain.place_like.entity.PlaceLike; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; + +public interface PlaceLikeRepository extends JpaRepository { + Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); + Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java new file mode 100644 index 00000000..9f5764f8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.place_like.service; + +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public interface PlaceLikeCommandService { + void deletePlaceLike(List placeIds); +} diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java new file mode 100644 index 00000000..d09f55f5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -0,0 +1,24 @@ +package com.otakumap.domain.place_like.service; + +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class PlaceLikeCommandServiceImpl implements PlaceLikeCommandService { + private final PlaceLikeRepository placeLikeRepository; + private final EntityManager entityManager; + + @Override + public void deletePlaceLike(List placeIds) { + placeLikeRepository.deleteAllByIdInBatch(placeIds); + entityManager.flush(); + entityManager.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java new file mode 100644 index 00000000..c8c9a601 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.place_like.service; + +import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; + +public interface PlaceLikeQueryService { + PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(Long userId, Long lastId, int limit); + boolean isPlaceLikeExist(Long id); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java new file mode 100644 index 00000000..39ee8b52 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -0,0 +1,66 @@ +package com.otakumap.domain.place_like.service; + +import com.otakumap.domain.place_like.converter.PlaceLikeConverter; +import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PlaceLikeQueryServiceImpl implements PlaceLikeQueryService { + private final PlaceLikeRepository placeLikeRepository; + private final UserRepository userRepository; + + @Override + public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(Long userId, Long lastId, int limit) { + List result; + Pageable pageable = PageRequest.of(0, limit + 1); + User user = userRepository.findById(userId).orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + + if (lastId.equals(0L)) { + result = (eventType == null) + ? eventLikeRepository.findAllByUserIsOrderByCreatedAtDesc(user, pageable).getContent() + : eventLikeRepository.findAllByUserIsAndEventTypeOrderByCreatedAtDesc(user, eventType, pageable).getContent(); + } else { + EventLike eventLike = eventLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + result = (eventType == null) + ? eventLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventLike.getCreatedAt(), pageable).getContent() + : eventLikeRepository.findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventType, eventLike.getCreatedAt(), pageable).getContent(); + } + return createEventLikePreviewListDTO(user, result, limit); + } + + + private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(User user, List placeLikes, int limit) { + boolean hasNext = placeLikes.size() > limit; + Long lastId = null; + if (hasNext) { + placeLikes = placeLikes.subList(0, placeLikes.size() - 1); + lastId = placeLikes.get(placeLikes.size() - 1).getId(); + } + List list = placeLikes + .stream() + .map(PlaceLikeConverter::placeLikePreViewDTO) + .collect(Collectors.toList()); + + return PlaceLikeConverter.placePlacePreViewListDTO(list, hasNext, lastId); + } + + @Override + public boolean isPlaceLikeExist(Long id) { + return placeLikeRepository.existsById(id); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index b27af9a5..2ee7c3fa 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -26,7 +26,10 @@ public enum ErrorStatus implements BaseErrorCode { EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), // 이벤트 상세 정보 관련 에러 - EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."); + EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."), + + // 명소 좋아요 관련 에러 + PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java new file mode 100644 index 00000000..692fa2e5 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/PlaceHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class PlaceHandler extends GeneralException { + public PlaceHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java new file mode 100644 index 00000000..a81cd186 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.PlaceLikeExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = PlaceLikeExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistPlaceLike { + String message() default "유효하지 않은 명소 ID가 포함되어 있습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java new file mode 100644 index 00000000..86fd7129 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java @@ -0,0 +1,38 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.place_like.service.PlaceLikeQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistPlaceLike; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class PlaceLikeExistValidator implements ConstraintValidator> { + private final PlaceLikeQueryService placeLikeQueryService; + + @Override + public void initialize(ExistPlaceLike constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List placeIds, ConstraintValidatorContext context) { + if (placeIds == null || placeIds.isEmpty()) { + return false; + } + + boolean isValid = placeIds.stream() + .allMatch(placeId -> placeLikeQueryService.isPlaceLikeExist(placeId)); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.PLACE_LIKE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} From a49cb5d65f09c2436d8326421ab4c17cd6ac3b3a Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 12 Jan 2025 12:55:31 +0900 Subject: [PATCH 034/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventShortReviewController.java | 11 +++++++++ .../converter/EventShortReviewConverter.java | 24 +++++++++++++++++++ .../dto/EventShortReviewResponseDTO.java | 23 ++++++++++++++++++ .../EventShortReviewRepository.java | 4 ++++ .../EventShortReviewCommandService.java | 2 ++ .../EventShortReviewCommandServiceImpl.java | 10 ++++++++ 6 files changed, 74 insertions(+) diff --git a/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java index 5bd4b75c..8a4293f3 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java @@ -4,6 +4,7 @@ import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; import com.otakumap.domain.eventShortReview.dto.EventShortReviewResponseDTO; import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.eventShortReview.repository.EventShortReviewRepository; import com.otakumap.domain.eventShortReview.service.EventShortReviewCommandService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -20,6 +21,7 @@ public class EventShortReviewController { private final EventShortReviewCommandService eventShortReviewCommandService; + private final EventShortReviewRepository eventShortReviewRepository; @Operation(summary = "이벤트 한 줄 리뷰 작성", description = "이벤트에 한 줄 리뷰를 작성합니다.") @PostMapping("/events/{eventId}/short-reviews") @@ -31,4 +33,13 @@ public ApiResponse createEve EventShortReview eventShortReview = eventShortReviewCommandService.createEventShortReview(eventId, request); return ApiResponse.onSuccess(EventShortReviewConverter.toNewEventShortReviewDTO(eventShortReview)); } + + @Operation(summary = "이벤트 한 줄 리뷰 목록 조회", description = "특정 이벤트의 한 줄 리뷰 목록을 불러옵니다.") + @GetMapping("/events/{eventId}/short-reviews") + @Parameters({ + @Parameter(name = "eventId", description = "특정 이벤트의 Id") + }) + public ApiResponse getEventShortReviewList(@PathVariable(name = "eventId") Long eventId, @RequestParam(name = "page")Integer page) { + return ApiResponse.onSuccess(EventShortReviewConverter.toEventShortReviewListDTO(eventShortReviewCommandService.getEventShortReviewsByEventId(eventId, page))); + } } diff --git a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java b/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java index 0ee4918e..86ad2c36 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java @@ -6,6 +6,10 @@ import com.otakumap.domain.eventShortReview.dto.EventShortReviewResponseDTO; import com.otakumap.domain.eventShortReview.entity.EventShortReview; import com.otakumap.domain.image.converter.ImageConverter; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; public class EventShortReviewConverter { public static EventShortReview toEventShortReview(EventShortReviewRequestDTO.NewEventShortReviewDTO request, Event event, User user) { @@ -27,4 +31,24 @@ public static EventShortReviewResponseDTO.NewEventShortReviewDTO toNewEventShort .profileImage(ImageConverter.toImageDTO(eventShortReview.getUser().getProfileImage())) .build(); } + + public static EventShortReviewResponseDTO.EventShortReviewDTO toEventShortReviewDTO(EventShortReview eventShortReview) { + return EventShortReviewResponseDTO.EventShortReviewDTO.builder() + .id(eventShortReview.getId()) + .content(eventShortReview.getContent()) + .rating(eventShortReview.getRating()) + .profileImage(ImageConverter.toImageDTO(eventShortReview.getUser().getProfileImage())) + .build(); + } + + public static EventShortReviewResponseDTO.EventShortReviewListDTO toEventShortReviewListDTO(Page reviewList) { + List reviewDTOList = reviewList.stream() + .map(EventShortReviewConverter::toEventShortReviewDTO).collect(Collectors.toList()); + + return EventShortReviewResponseDTO.EventShortReviewListDTO.builder() + .eventShortReviewList(reviewDTOList) + .currentPage(reviewList.getNumber()) + .totalPages(reviewList.getTotalPages()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java index 133b3286..d2bf4af8 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java @@ -6,6 +6,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; + public class EventShortReviewResponseDTO { @Builder @@ -22,4 +24,25 @@ public static class NewEventShortReviewDTO { int likes; int dislikes; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventShortReviewListDTO { + List eventShortReviewList; + Integer currentPage; + Integer totalPages; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventShortReviewDTO { + Long id; + String content; + Float rating; + ImageResponseDTO.ImageDTO profileImage; + } } diff --git a/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java b/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java index 3d2fece1..437abe24 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java @@ -1,7 +1,11 @@ package com.otakumap.domain.eventShortReview.repository; +import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; public interface EventShortReviewRepository extends JpaRepository { + Page findAllByEvent(Event event, PageRequest pageRequest); } diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java index 0aa4904e..e16ba531 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java @@ -2,7 +2,9 @@ import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import org.springframework.data.domain.Page; public interface EventShortReviewCommandService { EventShortReview createEventShortReview(Long eventId, EventShortReviewRequestDTO.NewEventShortReviewDTO request); + Page getEventShortReviewsByEventId(Long eventId, Integer page); } diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java index 17e7ac58..f88121e7 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java @@ -12,6 +12,8 @@ import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,4 +38,12 @@ public EventShortReview createEventShortReview(Long eventId, EventShortReviewReq return eventShortReviewRepository.save(eventShortReview); } + + @Override + public Page getEventShortReviewsByEventId(Long eventId, Integer page) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + + return eventShortReviewRepository.findAllByEvent(event, PageRequest.of(page, 4)); + } } From 896efcc3b7c673f8698cc9d8ea5314a38ac663db Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 13:04:27 +0900 Subject: [PATCH 035/516] =?UTF-8?q?Fix:=20converter=EC=97=90=20likes=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 1 + .../place_short_review/converter/PlaceShortReviewConverter.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 807d93ec..5fd1328a 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -38,6 +38,7 @@ public ApiResponse getPlace } @PostMapping("/places/{placeId}/short-review") + @Operation(summary = "특정 명소의 한 줄 리뷰 목록 작성 API", description = "특정 명소의 한 줄 리뷰를 작성하는 API입니다.") public ApiResponse createReview( @PathVariable Long placeId, @RequestBody @Valid PlaceShortReviewRequestDTO.CreateDTO request) { diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index 7d13b1d6..b0d2ce41 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -66,6 +66,8 @@ public static PlaceShortReview toPlaceShortReview(PlaceShortReviewRequestDTO.Cre .place(place) .rating(request.getRating()) .content(request.getContent()) + .dislikes(0L) + .likes(0L) .build(); } } From dae6c40b1a4be8d14537430f3c3c9478574fcc26 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 15:11:20 +0900 Subject: [PATCH 036/516] =?UTF-8?q?Refactor:=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 5fd1328a..268d0af0 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -33,7 +33,7 @@ public class PlaceShortReviewController { @Parameters({ @Parameter(name = "placeId", description = "명소의 아이디입니다.") }) - public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page) { + public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page")Integer page) { return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); } From e1c7ef27eee2701314d70f0280d1a3213458ac25 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 15:12:44 +0900 Subject: [PATCH 037/516] =?UTF-8?q?Refactor:=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 268d0af0..f6c2c08b 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -22,6 +22,8 @@ @Validated @RequestMapping("/api") public class PlaceShortReviewController { + + private final PlaceShortReviewCommandService placeShortReviewCommandService; private final PlaceShortReviewQueryService placeShortReviewQueryService; @@ -33,7 +35,7 @@ public class PlaceShortReviewController { @Parameters({ @Parameter(name = "placeId", description = "명소의 아이디입니다.") }) - public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page")Integer page) { + public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); } From d1a7a9c0e0102bcecd7e8b125aec95e4008a7c0d Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 12 Jan 2025 16:15:58 +0900 Subject: [PATCH 038/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceReviewController.java | 29 ++++++++++++++ .../converter/PlaceReviewConverter.java | 30 ++++++++++++++ .../dto/PlaceReviewRequestDTO.java | 19 +++++++++ .../dto/PlaceReviewResponseDTO.java | 21 ++++++++++ .../place_review/entity/PlaceReview.java | 39 +++++++++++++++++++ .../repository/PlaceReviewRepository.java | 7 ++++ .../service/PlaceReviewCommandService.java | 8 ++++ .../PlaceReviewCommandServiceImpl.java | 38 ++++++++++++++++++ 8 files changed, 191 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java create mode 100644 src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java create mode 100644 src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java create mode 100644 src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java create mode 100644 src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java new file mode 100644 index 00000000..88a84d51 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.place_review.controller; + +import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; +import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.service.PlaceReviewCommandService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class PlaceReviewController { + private final PlaceReviewCommandService placeReviewCommandService; + + @PostMapping("/review") + @Operation(summary = "리뷰 작성") + public ApiResponse createReview(@RequestBody @Valid PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { + PlaceReview placeReview = placeReviewCommandService.createReview(request); + return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java new file mode 100644 index 00000000..b92e1bb7 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.place_review.converter; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; +import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.user.entity.User; + +import java.time.LocalDateTime; + +public class PlaceReviewConverter { + public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { + return PlaceReviewResponseDTO.ReviewCreateResponseDTO.builder() + .reviewId(placeReview.getId()) + .title(placeReview.getTitle()) + .content(placeReview.getContent()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request, User user, Place place) { + return PlaceReview.builder() + .user(user) + .place(place) + .title(request.getTitle()) + .content(request.getContent()) + .view(0L) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java new file mode 100644 index 00000000..304c8abf --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java @@ -0,0 +1,19 @@ +package com.otakumap.domain.place_review.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +public class PlaceReviewRequestDTO { + @Getter + public static class ReviewCreateRequestDTO { + private Long userId; // 토큰 사용 전 임시로 사용 + private Long placeId; // 임시 + @NotBlank + @Size(max = 20) + private String title; + @NotBlank + private String content; + // TODO: 이미지, 장소 활용 + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java new file mode 100644 index 00000000..b7e0aaab --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.place_review.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class PlaceReviewResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewCreateResponseDTO { + private Long reviewId; + private String title; + private String content; + LocalDateTime createdAt; + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java new file mode 100644 index 00000000..2f9e0c4a --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -0,0 +1,39 @@ +package com.otakumap.domain.place_review.entity; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicInsert +@DynamicUpdate +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class PlaceReview extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String title; + + @Column(nullable = false, length = 3000) + private String content; + + @Column(columnDefinition = "bigint default 0 not null") + private Long view; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; +} diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java new file mode 100644 index 00000000..092cc8e6 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place_review.repository; + +import com.otakumap.domain.place_review.entity.PlaceReview; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceReviewRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java new file mode 100644 index 00000000..bcea5f22 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.place_review.service; + +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; + +public interface PlaceReviewCommandService { + PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); +} diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java new file mode 100644 index 00000000..486b442c --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.place_review.service; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService { + private final PlaceReviewRepository placeReviewRepository; + private final PlaceRepository placeRepository; + private final UserRepository userRepository; + + @Override + @Transactional + public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { + User user = userRepository.findById(request.getUserId()) + .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + + Place place = placeRepository.findById(request.getPlaceId()) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + PlaceReview placeReview = PlaceReviewConverter.toPlaceReview(request, user, place); + + return placeReviewRepository.save(placeReview); + } +} From e39a063eb21b883d5a8153f74af0a09d8349ebd9 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sun, 12 Jan 2025 21:28:21 +0900 Subject: [PATCH 039/516] =?UTF-8?q?Feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceLikeConverter.java | 1 + .../repository/PlaceLikeRepository.java | 3 ++ .../service/PlaceLikeQueryServiceImpl.java | 52 ++++++++++--------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 3d75fe06..21031bc1 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -15,6 +15,7 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(Place .build(); } + public static PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikePreViewListDTO(List placeLikes, boolean hasNext, Long lastId) { return PlaceLikeResponseDTO.PlaceLikePreViewListDTO.builder() .placeLikes(placeLikes) diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index 5594df93..da8e2c34 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -7,8 +7,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.time.LocalDateTime; +import java.util.List; public interface PlaceLikeRepository extends JpaRepository { Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); + + Page findByUserIdAndIdLessThanOrderByIdDesc(Long userId, Long lastId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index 39ee8b52..69ed4b87 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -2,6 +2,7 @@ import com.otakumap.domain.place_like.converter.PlaceLikeConverter; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; @@ -9,6 +10,7 @@ import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -26,39 +28,41 @@ public class PlaceLikeQueryServiceImpl implements PlaceLikeQueryService { @Override public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(Long userId, Long lastId, int limit) { - List result; - Pageable pageable = PageRequest.of(0, limit + 1); - User user = userRepository.findById(userId).orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); - if (lastId.equals(0L)) { - result = (eventType == null) - ? eventLikeRepository.findAllByUserIsOrderByCreatedAtDesc(user, pageable).getContent() - : eventLikeRepository.findAllByUserIsAndEventTypeOrderByCreatedAtDesc(user, eventType, pageable).getContent(); - } else { - EventLike eventLike = eventLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); - result = (eventType == null) - ? eventLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventLike.getCreatedAt(), pageable).getContent() - : eventLikeRepository.findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventType, eventLike.getCreatedAt(), pageable).getContent(); - } - return createEventLikePreviewListDTO(user, result, limit); + Pageable pageable = PageRequest.of(0, limit+1); + Page placeLikePage = placeLikeRepository.findByUserIdAndIdLessThanOrderByIdDesc(userId, lastId, pageable); + + List placeLikeDTOs = placeLikePage.getContent().stream() + .map(placeLike -> new PlaceLikeResponseDTO.PlaceLikePreViewDTO( + placeLike.getId(), + placeLike.getUser().getId(), + placeLike.getPlace().getId(), + placeLike.getIsFavorite() + )) + .collect(Collectors.toList()); + + boolean hasNext = placeLikePage.hasNext(); + Long newLastId = placeLikeDTOs.isEmpty() ? null : placeLikeDTOs.get(placeLikeDTOs.size() - 1).getId(); + + return new PlaceLikeResponseDTO.PlaceLikePreViewListDTO(placeLikeDTOs, hasNext, newLastId); } - private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(User user, List placeLikes, int limit) { - boolean hasNext = placeLikes.size() > limit; - Long lastId = null; - if (hasNext) { - placeLikes = placeLikes.subList(0, placeLikes.size() - 1); - lastId = placeLikes.get(placeLikes.size() - 1).getId(); - } - List list = placeLikes - .stream() + + private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(User user, Page placeLikePage) { + List list = placeLikePage.getContent().stream() .map(PlaceLikeConverter::placeLikePreViewDTO) .collect(Collectors.toList()); - return PlaceLikeConverter.placePlacePreViewListDTO(list, hasNext, lastId); + boolean hasNext = placeLikePage.hasNext(); + Long lastId = list.isEmpty() ? null : list.get(list.size() - 1).getId(); + + return PlaceLikeConverter.placeLikePreViewListDTO(list, hasNext, lastId); } + @Override public boolean isPlaceLikeExist(Long id) { return placeLikeRepository.existsById(id); From d7ecf8fffe354f7a9dc949bf653bf3930129733a Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:44:31 +0900 Subject: [PATCH 040/516] =?UTF-8?q?Feat:=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20Handler=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/exception/handler/AuthHandler.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/AuthHandler.java diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/AuthHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AuthHandler.java new file mode 100644 index 00000000..8b1989a6 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AuthHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class AuthHandler extends GeneralException { + public AuthHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 3826c74e621a6b9fd71b13cf34fbfce6342e3e4f Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:44:56 +0900 Subject: [PATCH 041/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/apiPayload/code/status/ErrorStatus.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 2ee7c3fa..89e44619 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -16,6 +16,12 @@ public enum ErrorStatus implements BaseErrorCode { _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + // 인증 관련 에러 + PASSWORD_NOT_EQUAL(HttpStatus.BAD_REQUEST, "AUTH4001", "비밀번호가 일치하지 않습니다."), + EMAIL_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "AUTH4002", "인증 코드가 만료되었습니다. 다시 요청해주세요."), + CODE_NOT_EQUAL(HttpStatus.BAD_REQUEST, "AUTH4003", "인증 코드가 올바르지 않습니다."), + EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AUTH5001", "메일 발송 중 오류가 발생했습니다."), + // 멤버 관련 에러 USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), @@ -31,7 +37,6 @@ public enum ErrorStatus implements BaseErrorCode { // 명소 좋아요 관련 에러 PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."); - private final HttpStatus httpStatus; private final String code; private final String message; From 1b0baedbf6162adf355d7fcb025490aab75e1d03 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:45:55 +0900 Subject: [PATCH 042/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=86=A1=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/global/config/AsyncConfig.java | 32 +++++++++ .../otakumap/global/config/MailConfig.java | 67 +++++++++++++++++++ .../otakumap/global/config/RedisConfig.java | 42 ++++++++++++ .../com/otakumap/global/util/RedisUtil.java | 35 ++++++++++ 4 files changed, 176 insertions(+) create mode 100644 src/main/java/com/otakumap/global/config/AsyncConfig.java create mode 100644 src/main/java/com/otakumap/global/config/MailConfig.java create mode 100644 src/main/java/com/otakumap/global/config/RedisConfig.java create mode 100644 src/main/java/com/otakumap/global/util/RedisUtil.java diff --git a/src/main/java/com/otakumap/global/config/AsyncConfig.java b/src/main/java/com/otakumap/global/config/AsyncConfig.java new file mode 100644 index 00000000..25771ab3 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/AsyncConfig.java @@ -0,0 +1,32 @@ +package com.otakumap.global.config; + +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +public class AsyncConfig implements AsyncConfigurer { + + @Override + @Bean(name = "mailExecutor") + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(5); + executor.setQueueCapacity(10); + executor.setThreadNamePrefix("Async MailExecutor-"); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/config/MailConfig.java b/src/main/java/com/otakumap/global/config/MailConfig.java new file mode 100644 index 00000000..4a735e41 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/MailConfig.java @@ -0,0 +1,67 @@ +package com.otakumap.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + @Value("${spring.mail.host}") + private String host; + + @Value("${spring.mail.port}") + private Integer port; + + @Value("${spring.mail.username}") + private String username; + + @Value("${spring.mail.password}") + private String password; + + @Value("${spring.mail.properties.mail.smtp.auth}") + private boolean auth; + + @Value("${spring.mail.properties.mail.smtp.starttls.enable}") + private boolean starttlsEnable; + + @Value("${spring.mail.properties.mail.smtp.starttls.required}") + private boolean starttlsRequired; + + @Value("${spring.mail.properties.mail.smtp.connectiontimeout}") + private int connectionTimeout; + + @Value("${spring.mail.properties.mail.smtp.timeout}") + private int timeout; + + @Value("${spring.mail.properties.mail.smtp.writetimeout}") + private int writeTimeout; + + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(host); + mailSender.setPort(port); + mailSender.setUsername(username); + mailSender.setPassword(password); + mailSender.setDefaultEncoding("UTF-8"); + mailSender.setJavaMailProperties(getMailProperties()); + + return mailSender; + } + + private Properties getMailProperties() { + Properties properties = new Properties(); + properties.put("mail.smtp.auth", auth); + properties.put("mail.smtp.starttls.enable", starttlsEnable); + properties.put("mail.smtp.starttls.required", starttlsRequired); + properties.put("mail.smtp.connectiontimeout", connectionTimeout); + properties.put("mail.smtp.timeout", timeout); + properties.put("mail.smtp.writetimeout", writeTimeout); + + return properties; + } +} diff --git a/src/main/java/com/otakumap/global/config/RedisConfig.java b/src/main/java/com/otakumap/global/config/RedisConfig.java new file mode 100644 index 00000000..df813134 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/RedisConfig.java @@ -0,0 +1,42 @@ +package com.otakumap.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + // 일반적인 key:value의 경우 시리얼라이저 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + + // Hash를 사용할 경우 시리얼라이저 + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new StringRedisSerializer()); + + // 모든 경우 + redisTemplate.setDefaultSerializer(new StringRedisSerializer()); + + return redisTemplate; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/util/RedisUtil.java b/src/main/java/com/otakumap/global/util/RedisUtil.java new file mode 100644 index 00000000..6bad0eee --- /dev/null +++ b/src/main/java/com/otakumap/global/util/RedisUtil.java @@ -0,0 +1,35 @@ +package com.otakumap.global.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@RequiredArgsConstructor +@Service +public class RedisUtil { + private final RedisTemplate redisTemplate; + private final StringRedisTemplate stringRedisTemplate; + + public String getData(String key) { + ValueOperations valueOperations = stringRedisTemplate.opsForValue(); + return valueOperations.get(key); + } + + public boolean existData(String key) { + return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); + } + + public void setDataExpire(String key, String value, long duration) { + ValueOperations valueOperations = stringRedisTemplate.opsForValue(); + Duration expireDuration = Duration.ofSeconds(duration); + valueOperations.set(key, value, expireDuration); + } + + public void deleteData(String key) { + stringRedisTemplate.delete(key); + } +} \ No newline at end of file From 0bab31ab2d2b0147b17660cff71746b1bd0eb09d Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:46:43 +0900 Subject: [PATCH 043/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=86=A1=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 9de9406a..10e916a8 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,10 @@ dependencies { // implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + + // 이메일 인증 기능 + implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } tasks.named('test') { From 1d2264e7c0a21d0e6552809db5c6c6bc384b76ca Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:47:18 +0900 Subject: [PATCH 044/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=86=A1=EC=9D=84=20=EC=9C=84=ED=95=9C=20yml=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ab7ce13c..cb70a924 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,4 +16,26 @@ spring: use_sql_comments: true hbm2ddl: auto: update - default_batch_fetch_size: 1000 \ No newline at end of file + default_batch_fetch_size: 1000 + mail: + host: ${MAIL_HOST} + port: ${MAIL_POST} + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true + connectiontimeout: 5000 + timeout: 5000 + writetimeout: 5000 + auth-code-expiration-millis: 1800000 # 30 * 60 * 1000 == 30? + data: + redis: + host: localhost + port: 6379 + repositories: + enabled: false \ No newline at end of file From 42656cdc3afa5cfa38dafe649a645fdbeecf45f9 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:48:23 +0900 Subject: [PATCH 045/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 52 +++++++++++++++ .../domain/auth/dto/AuthRequestDTO.java | 65 +++++++++++++++++++ .../domain/auth/dto/AuthResponseDTO.java | 44 +++++++++++++ .../auth/service/AuthCommandService.java | 13 ++++ .../auth/service/AuthCommandServiceImpl.java | 64 ++++++++++++++++++ .../domain/user/converter/UserConverter.java | 48 ++++++++++++++ .../user/repository/UserRepository.java | 2 + 7 files changed, 288 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/auth/controller/AuthController.java create mode 100644 src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java create mode 100644 src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/user/converter/UserConverter.java diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java new file mode 100644 index 00000000..13129000 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -0,0 +1,52 @@ +package com.otakumap.domain.auth.controller; + +import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.auth.service.AuthCommandService; +import com.otakumap.domain.user.converter.UserConverter; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.mail.MessagingException; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + private final AuthCommandService authCommandService; + + @Operation(summary = "회원가입", description = "회원가입 기능입니다.") + @PostMapping("/signup") + public ApiResponse signup(@RequestBody @Valid AuthRequestDTO.SignupDTO request) { + return ApiResponse.onSuccess(UserConverter.toSignupResultDTO(authCommandService.signup(request))); + } + + @Operation(summary = "닉네임 중복 확인", description = "닉네임 중복 확인 기능입니다.") + @PostMapping("/check-nickname") + public ApiResponse checkNickname(@RequestBody @Valid AuthRequestDTO.CheckNicknameDTO request) { + return ApiResponse.onSuccess(UserConverter.toCheckNicknameResultDTO(authCommandService.checkNickname(request))); + } + + @Operation(summary = "아이디 중복 확인", description = "아이디 중복 확인 기능입니다.") + @PostMapping("/check-id") + public ApiResponse checkId(@RequestBody @Valid AuthRequestDTO.CheckIdDTO request) { + return ApiResponse.onSuccess(UserConverter.toCheckIdResultDTO(authCommandService.checkId(request))); + } + + @Operation(summary = "이메일 인증 메일 전송", description = "이메일 인증을 위한 메일 전송 기능입니다.") + @PostMapping("/verify-email") + public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { + return ApiResponse.onSuccess(authCommandService.verifyEmail(request)); + } + + @Operation(summary = "이메일 코드 인증", description = "이메일 코드 인증 기능입니다.") + @PostMapping("/verify-code") + public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyCodeDTO request) { + return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request))); + } +} diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java new file mode 100644 index 00000000..a1bcbb56 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -0,0 +1,65 @@ +package com.otakumap.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; + +public class AuthRequestDTO { + @Getter + public static class SignupDTO { + @NotBlank(message = "이름 입력은 필수입니다.") + @Schema(description = "name", example = "오타쿠맵") + String name; + + @NotBlank(message = "닉네임 입력은 필수입니다.") + @Schema(description = "nickname", example = "오타쿠") + String nickname; + + @NotBlank(message = "아이디 입력은 필수입니다.") + @Schema(description = "userId", example = "otakumap1234") + String userId; + + @NotBlank(message = "이메일 입력은 필수입니다.") + @Schema(description = "email", example = "otakumap1234@gmail.com") + @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.") + String email; + + @NotBlank(message = "비밀번호 입력은 필수입니다.") + @Schema(description = "password", example = "otakumap1234!") + @Pattern(regexp = "^(?:(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,})|(?:(?=.*[a-zA-Z])(?=.*\\d).{10,})|(?:(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{10,})|(?:(?=.*\\d)(?=.*[!@#$%^&*]).{10,})$", + message = "영문, 숫자, 특수문자 중 2종류 이상을 조합하여 최소 10자리 이상이거나, 영문, 숫자, 특수문자 모두를 포함하여 최소 8자리 이상 입력해야 합니다.") + String password; + + @NotBlank(message = "비밀번호 재확인 입력은 필수 입니다.") + @Schema(description = "passwordCheck", example = "otakumap1234!") + String passwordCheck; + } + + @Getter + public static class CheckNicknameDTO { + @NotNull + String nickname; + } + + @Getter + public static class CheckIdDTO { + @NotNull + String userId; + } + + @Getter + public static class VerifyEmailDTO { + @NotNull + String email; + } + + @Getter + public static class VerifyCodeDTO { + @NotNull + String code; + @NotNull + String email; + } +} diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java new file mode 100644 index 00000000..136a8b44 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java @@ -0,0 +1,44 @@ +package com.otakumap.domain.auth.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class AuthResponseDTO { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class SignupResultDTO { + Long id; + LocalDateTime createdAt; + } + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CheckNicknameResultDTO { + boolean isDuplicated; + } + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CheckIdResultDTO { + boolean isDuplicated; + } + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class VerifyCodeResultDTO { + boolean isVerified; + } +} diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java new file mode 100644 index 00000000..913854e9 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -0,0 +1,13 @@ +package com.otakumap.domain.auth.service; + +import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.user.entity.User; +import jakarta.mail.MessagingException; + +public interface AuthCommandService { + User signup(AuthRequestDTO.SignupDTO request); + boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request); + boolean checkId(AuthRequestDTO.CheckIdDTO request); + String verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; + boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); +} diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java new file mode 100644 index 00000000..a3606845 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -0,0 +1,64 @@ +package com.otakumap.domain.auth.service; + +import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.user.converter.UserConverter; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import com.otakumap.global.util.RedisUtil; +import jakarta.mail.MessagingException; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.MailException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthCommandServiceImpl implements AuthCommandService { + private final UserRepository userRepository; + private final RedisUtil redisUtil; + private final MailService mailService; + + @Override + public User signup(AuthRequestDTO.SignupDTO request) { + if(!request.getPassword().equals(request.getPasswordCheck())) { + throw new AuthHandler(ErrorStatus.PASSWORD_NOT_EQUAL); + } + + User user = UserConverter.toUser(request); + return userRepository.save(user); + } + + @Override + public boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request) { + System.out.println(request.getNickname()); + return userRepository.existsByNickname(request.getNickname()); + } + + @Override + public boolean checkId(AuthRequestDTO.CheckIdDTO request) { + return userRepository.existsByUserId(request.getUserId()); + } + + @Override + public String verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { + try { + mailService.sendEmail(request.getEmail()); + } catch (MailException e) { + throw new AuthHandler(ErrorStatus.EMAIL_SEND_FAILED); + } + return "이메일 인증이 성공적으로 완료되었습니다."; + } + + @Override + public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request) { + String authCode = redisUtil.getData(request.getEmail()); + if (authCode == null) { + throw new AuthHandler(ErrorStatus.EMAIL_CODE_EXPIRED); + } + if (!authCode.equals(request.getCode())) { + throw new AuthHandler(ErrorStatus.CODE_NOT_EQUAL); + } + return true; + } +} diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java new file mode 100644 index 00000000..c086c53c --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -0,0 +1,48 @@ +package com.otakumap.domain.user.converter; + +import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.entity.enums.Role; +import com.otakumap.domain.user.entity.enums.UserStatus; + +import java.time.LocalDateTime; + +public class UserConverter { + public static User toUser(AuthRequestDTO.SignupDTO request) { + return User.builder() + .name(request.getName()) + .nickname(request.getNickname()) + .userId(request.getUserId()) + .email(request.getEmail()) + .password(request.getPassword()) + .role(Role.USER) + .status(UserStatus.ACTIVE) + .build(); + } + + public static AuthResponseDTO.SignupResultDTO toSignupResultDTO(User user) { + return AuthResponseDTO.SignupResultDTO.builder() + .id(user.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static AuthResponseDTO.CheckNicknameResultDTO toCheckNicknameResultDTO(boolean isDuplicated) { + return AuthResponseDTO.CheckNicknameResultDTO.builder() + .isDuplicated(isDuplicated) + .build(); + } + + public static AuthResponseDTO.CheckIdResultDTO toCheckIdResultDTO(boolean isDuplicated) { + return AuthResponseDTO.CheckIdResultDTO.builder() + .isDuplicated(isDuplicated) + .build(); + } + + public static AuthResponseDTO.VerifyCodeResultDTO toVerifyCodeResultDTO(boolean isVerified) { + return AuthResponseDTO.VerifyCodeResultDTO.builder() + .isVerified(isVerified) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java index 9cd7df16..33c92e83 100644 --- a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java +++ b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { + boolean existsByNickname(String nickname); + boolean existsByUserId(String userId); } From e79ba2d86d0e8242199141dccf4071d5595a9452 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:48:38 +0900 Subject: [PATCH 046/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=97=AC=EB=B6=80=20=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 0e2fa14b..504796d8 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -55,10 +55,6 @@ public class User extends BaseEntity { @Column(columnDefinition = "VARCHAR(10) DEFAULT 'USER'", nullable = false) private Role role; - @ColumnDefault("FALSE") - @Column(name = "is_email_verified") - private Boolean isEmailVerified; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; From e8840470060256b5d91f5062bca564c5b3b96b69 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:49:17 +0900 Subject: [PATCH 047/516] =?UTF-8?q?Feat:=20=EB=B9=84=EB=8F=99=EA=B8=B0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/OtakumapApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/OtakumapApplication.java b/src/main/java/com/otakumap/OtakumapApplication.java index 1ed786ae..504ffa81 100644 --- a/src/main/java/com/otakumap/OtakumapApplication.java +++ b/src/main/java/com/otakumap/OtakumapApplication.java @@ -3,8 +3,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableAsync; @EnableJpaAuditing +@EnableAsync @SpringBootApplication public class OtakumapApplication { From 945223a0540d415696a36002f65235b89feba8ad Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:51:00 +0900 Subject: [PATCH 048/516] =?UTF-8?q?Feat:=20=EC=9D=B8=EC=A6=9D=20=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B0=9C=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/MailService.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/auth/service/MailService.java diff --git a/src/main/java/com/otakumap/domain/auth/service/MailService.java b/src/main/java/com/otakumap/domain/auth/service/MailService.java new file mode 100644 index 00000000..fb01a3b2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/MailService.java @@ -0,0 +1,75 @@ +package com.otakumap.domain.auth.service; + +import com.otakumap.global.util.RedisUtil; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.MailException; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Random; + +@Service +@RequiredArgsConstructor +public class MailService { + private final JavaMailSender javaMailSender; + + private final RedisUtil redisUtil; + + @Value("${spring.mail.username}") + private String senderEmail; + + // 인증 코드 생성 + private String createCode() { + int leftLimit = 48; // number '0' + int rightLimit = 122; // alphabet 'z' + int targetStringLength = 6; + Random random = new Random(); + + return random.ints(leftLimit, rightLimit + 1) + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 | i >= 97)) + .limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + + // 이메일 폼 생성 + private MimeMessage createEmailForm(String email, String code) throws MessagingException { + MimeMessage message = javaMailSender.createMimeMessage(); + + message.setFrom(senderEmail); + message.addRecipients(MimeMessage.RecipientType.TO, email); + + message.setSubject("오타쿠맵 이메일 인증 안내"); + String body = ""; + body += "

회원가입을 위한 이메일 인증을 진행합니다.

"; + body += "

아래 발급된 인증번호를 입력하여 인증을 완료해주세요.

"; + body += "
"; + body += "

인증번호 " + code + "

"; + message.setText(body, "UTF-8", "html"); + + // Redis 에 해당 인증코드 인증 시간 설정(30분) + redisUtil.setDataExpire(email, code, 60 * 30L); + + return message; + } + + // 메일 발송 + @Async + public void sendEmail(String sendEmail) throws MessagingException { + if (redisUtil.existData(sendEmail)) { + redisUtil.deleteData(sendEmail); + } + String authCode = createCode(); + MimeMessage message = createEmailForm(sendEmail, authCode); // 메일 생성 + try { + javaMailSender.send(message); // 메일 발송 + } catch (MailException e) { + throw new MailSendException(e.getMessage()); + } + } +} From 8a5149ac12ffb43c62173f49d50517bdf95b10b8 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 13:51:21 +0900 Subject: [PATCH 049/516] =?UTF-8?q?Feat:=20Redis=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..de93935f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +--- +version: '3.8' +services: + redis: + image: redis:latest + container_name: redis + ports: + - "6379:6379" \ No newline at end of file From d3cd05757d527531ac41afaa57f3c06eefdb8e8c Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 14:06:21 +0900 Subject: [PATCH 050/516] =?UTF-8?q?Refactor:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cb70a924..bbc7b0ea 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,7 +19,7 @@ spring: default_batch_fetch_size: 1000 mail: host: ${MAIL_HOST} - port: ${MAIL_POST} + port: ${MAIL_PORT} username: ${MAIL_USERNAME} password: ${MAIL_PASSWORD} properties: From 8eef7074f47b117d2b47a16c873448d04645947e Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 14:24:47 +0900 Subject: [PATCH 051/516] =?UTF-8?q?Feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthCommandServiceImpl.java | 8 +++++--- src/main/java/com/otakumap/domain/user/entity/User.java | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index a3606845..6bc68a98 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -10,12 +10,14 @@ import jakarta.mail.MessagingException; import lombok.RequiredArgsConstructor; import org.springframework.mail.MailException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class AuthCommandServiceImpl implements AuthCommandService { private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; private final RedisUtil redisUtil; private final MailService mailService; @@ -24,9 +26,9 @@ public User signup(AuthRequestDTO.SignupDTO request) { if(!request.getPassword().equals(request.getPasswordCheck())) { throw new AuthHandler(ErrorStatus.PASSWORD_NOT_EQUAL); } - - User user = UserConverter.toUser(request); - return userRepository.save(user); + User newUser = UserConverter.toUser(request); + newUser.encodePassword(passwordEncoder.encode(request.getPassword())); + return userRepository.save(newUser); } @Override diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 504796d8..c03d9ada 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -27,7 +27,7 @@ public class User extends BaseEntity { @Column(length = 20, nullable = false) private String userId; - @Column(length = 20, nullable = false) + @Column(nullable = false) private String password; @Column(length = 30, nullable = false) @@ -58,4 +58,8 @@ public class User extends BaseEntity { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; + + public void encodePassword(String password) { + this.password = password; + } } From df595b7cb68e3865d766b4be9d90b82b2cc082af Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 22:00:51 +0900 Subject: [PATCH 052/516] =?UTF-8?q?Feat:=20=EC=84=A4=EC=A0=95=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 57 ++++++++++++------- .../com/otakumap/global/config/WebConfig.java | 20 +++++++ 2 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/otakumap/global/config/WebConfig.java diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 49b3667e..2b1e0978 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -1,45 +1,58 @@ package com.otakumap.global.config; +import com.otakumap.domain.auth.jwt.filter.JwtFilter; +import com.otakumap.domain.auth.jwt.handler.JwtAccessDeniedHandler; +import com.otakumap.domain.auth.jwt.handler.JwtAuthenticationEntryPoint; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetailsService; +import com.otakumap.domain.auth.jwt.util.JwtProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration +@EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final JwtProvider jwtProvider; + private final PrincipalDetailsService principalDetailsService; - // 현재 oauth와 jwt 설정 안 해놔서 작동 안 하기 때문에 해당 코드는 주석처리함 -// private final JwtAuthenticationFilter jwtAuthenticationFilter; -// private final AuthenticationProvider authenticationProvider; -// private final CustomOAuth2UserService oAuth2UserService; -// private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + private final String[] allowUrl = { + "/", + "/swagger-ui/**", + "/swagger-resources/**", + "/v3/api-docs/**", + "/api/auth/**" + }; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http + //crsf 보안 비활성화 .csrf(csrf -> csrf.disable()) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll() - .requestMatchers("/", "/home", "/signup", "/members/signup", "/css/**").permitAll() - .requestMatchers("/api/auth/**").permitAll() - .requestMatchers("/admin/**").hasRole("ADMIN") - .anyRequest().authenticated() - ) -// .authenticationProvider(authenticationProvider) -// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) -// .oauth2Login(oauth2 -> oauth2 -// .loginPage("/login") -// .userInfoEndpoint(userInfo -> userInfo.userService(oAuth2UserService)) -// .successHandler(oAuth2LoginSuccessHandler) -// ) - ; - + .authorizeHttpRequests(request -> request + .requestMatchers(allowUrl).permitAll() + .anyRequest().authenticated()) + //기본 폼 로그인 비활성화 + .formLogin((form) -> form.disable()) + // BasicHttp 비활성화 + .httpBasic(AbstractHttpConfigurer::disable) + //JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가 + .addFilterBefore(new JwtFilter(jwtProvider, principalDetailsService), UsernamePasswordAuthenticationFilter.class) + // 예외 처리 설정 + .exceptionHandling(exception -> exception + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .accessDeniedHandler(jwtAccessDeniedHandler) + ); return http.build(); } diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java new file mode 100644 index 00000000..ab0bc469 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -0,0 +1,20 @@ +package com.otakumap.global.config; + +import com.otakumap.domain.auth.jwt.resolver.AuthenticatedUserResolver; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + private final AuthenticatedUserResolver authenticatedUserResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(authenticatedUserResolver); + } +} \ No newline at end of file From 744301147a75e6f8e1a57dd696ec8ec2c3221a21 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 23:14:09 +0900 Subject: [PATCH 053/516] =?UTF-8?q?Feat:=20JWT=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 + .../auth/jwt/annotation/CurrentUser.java | 14 ++ .../otakumap/domain/auth/jwt/dto/JwtDTO.java | 11 ++ .../domain/auth/jwt/filter/JwtFilter.java | 73 +++++++++ .../jwt/handler/JwtAccessDeniedHandler.java | 34 +++++ .../handler/JwtAuthenticationEntryPoint.java | 33 ++++ .../resolver/AuthenticatedUserResolver.java | 39 +++++ .../jwt/userdetails/PrincipalDetails.java | 57 +++++++ .../userdetails/PrincipalDetailsService.java | 23 +++ .../domain/auth/jwt/util/JwtProvider.java | 142 ++++++++++++++++++ .../user/repository/UserRepository.java | 3 + .../domain/user/service/UserQueryService.java | 7 + .../user/service/UserQueryServiceImpl.java | 19 +++ .../apiPayload/code/status/ErrorStatus.java | 3 + .../global/config/SecurityConfig.java | 5 +- .../com/otakumap/global/util/RedisUtil.java | 27 ++-- src/main/resources/application.yml | 9 +- 17 files changed, 489 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/dto/JwtDTO.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAccessDeniedHandler.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAuthenticationEntryPoint.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java create mode 100644 src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java create mode 100644 src/main/java/com/otakumap/domain/user/service/UserQueryService.java create mode 100644 src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java diff --git a/build.gradle b/build.gradle index 10e916a8..546d2140 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,11 @@ dependencies { // 이메일 인증 기능 implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java b/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java new file mode 100644 index 00000000..0c484502 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.auth.jwt.annotation; + +import io.swagger.v3.oas.annotations.Parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Parameter(hidden = true) +public @interface CurrentUser { +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/dto/JwtDTO.java b/src/main/java/com/otakumap/domain/auth/jwt/dto/JwtDTO.java new file mode 100644 index 00000000..84ae03d7 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/dto/JwtDTO.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.auth.jwt.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class JwtDTO { + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java b/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java new file mode 100644 index 00000000..259315dc --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java @@ -0,0 +1,73 @@ +package com.otakumap.domain.auth.jwt.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetailsService; +import com.otakumap.domain.auth.jwt.util.JwtProvider; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.GeneralException; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import com.otakumap.global.util.RedisUtil; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtFilter extends OncePerRequestFilter { + private final JwtProvider jwtProvider; + private final RedisUtil redisUtil; + private final PrincipalDetailsService principalDetailsService; + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { + try { + String accessToken = jwtProvider.resolveAccessToken(request); + + //JWT 유효성 검증 + if(accessToken != null && jwtProvider.validateToken(accessToken)) { + String blackListValue = (String) redisUtil.get(accessToken); + if (blackListValue != null && blackListValue.equals("logout")) { + throw new AuthHandler(ErrorStatus.TOKEN_LOGGED_OUT); + } + String userId = jwtProvider.getUserId(accessToken); + + //유저와 토큰 일치 시 userDetails 생성 + UserDetails userDetails = principalDetailsService.loadUserByUsername(userId); + if (userDetails != null) { + //userDetails, password, role -> 접근 권한 인증 Token 생성 + Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + + //현재 Request의 Security Context에 접근 권한 설정 + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + throw new AuthHandler(ErrorStatus.USER_NOT_FOUND); + } + } + // 다음 필터로 넘기기 + filterChain.doFilter(request, response); + } catch (GeneralException e) { + BaseErrorCode code = e.getCode(); + response.setContentType("application/json; charset=UTF-8"); + response.setStatus(code.getReasonHttpStatus().getHttpStatus().value()); + + ApiResponse errorResponse = ApiResponse.onFailure( + code.getReasonHttpStatus().getCode(), + code.getReasonHttpStatus().getMessage(), + e.getMessage()); + + ObjectMapper om = new ObjectMapper(); + om.writeValue(response.getOutputStream(), errorResponse); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAccessDeniedHandler.java b/src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAccessDeniedHandler.java new file mode 100644 index 00000000..b57acffd --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAccessDeniedHandler.java @@ -0,0 +1,34 @@ +package com.otakumap.domain.auth.jwt.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j(topic = "FORBIDDEN_EXCEPTION_HANDLER") +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + log.warn("Access Denied: ", accessDeniedException); + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpStatus.FORBIDDEN.value()); + ApiResponse errorResponse = ApiResponse.onFailure( + ErrorStatus._FORBIDDEN.getReasonHttpStatus().getCode(), + ErrorStatus._FORBIDDEN.getReasonHttpStatus().getMessage(), + accessDeniedException.getMessage() + ); + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(response.getOutputStream(), errorResponse); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAuthenticationEntryPoint.java b/src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..9cc11039 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/handler/JwtAuthenticationEntryPoint.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.auth.jwt.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j(topic = "UNAUTHORIZATION_EXCEPTION_HANDLER") +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { + log.error("No Authorities", authException); + + response.setContentType("application/json; charset=UTF-8"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + ApiResponse errorResponse = ApiResponse.onFailure( + ErrorStatus._UNAUTHORIZED.getReasonHttpStatus().getCode(), + ErrorStatus._UNAUTHORIZED.getReasonHttpStatus().getMessage(), + authException.getMessage() + ); + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(response.getOutputStream(), errorResponse); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java b/src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java new file mode 100644 index 00000000..72b32379 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java @@ -0,0 +1,39 @@ +package com.otakumap.domain.auth.jwt.resolver; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.service.UserQueryService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +@RequiredArgsConstructor +@Slf4j +public class AuthenticatedUserResolver implements HandlerMethodArgumentResolver { + private final UserQueryService userQueryService; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(CurrentUser.class) && parameter.getParameterType().isAssignableFrom(User.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null) { + PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); + return userQueryService.getUserByUserId(principalDetails.getUsername()); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java new file mode 100644 index 00000000..a36497b0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java @@ -0,0 +1,57 @@ +package com.otakumap.domain.auth.jwt.userdetails; + +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class PrincipalDetails implements UserDetails { + private final User user; + + @Override + public Collection getAuthorities() { + List roles = new ArrayList<>(); + roles.add(user.getRole().toString()); + + return roles.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + @Override + public String getUsername() { + return user.getUserId(); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java new file mode 100644 index 00000000..a428ac8d --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java @@ -0,0 +1,23 @@ +package com.otakumap.domain.auth.jwt.userdetails; + +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PrincipalDetailsService implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { + User user = userRepository.findByUserId(userId).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + return new PrincipalDetails(user); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java b/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java new file mode 100644 index 00000000..a7f3970d --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java @@ -0,0 +1,142 @@ +package com.otakumap.domain.auth.jwt.util; + +import com.otakumap.domain.auth.jwt.dto.JwtDTO; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetailsService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import com.otakumap.global.util.RedisUtil; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class JwtProvider { + private final PrincipalDetailsService userDetailsService; + private final SecretKey secret; + private final Long accessExpiration; + private final Long refreshExpiration; + private final RedisUtil redisUtil; + + public JwtProvider( + PrincipalDetailsService userDetailsService, + @Value("${spring.jwt.secret}") String secret, + @Value("${spring.jwt.token.access-expiration-time}") Long accessExpiration, + @Value("${spring.jwt.token.refresh-expiration-time}") Long refreshExpiration, + RedisUtil redisUtil) { + this.userDetailsService = userDetailsService; + this.secret = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + this.accessExpiration = accessExpiration; + this.refreshExpiration = refreshExpiration; + this.redisUtil = redisUtil; + } + + // AccessToken 생성 + public String createAccessToken(PrincipalDetails userDetails) { + Instant issuedAt = Instant.now(); + Instant expiredAt = issuedAt.plusMillis(accessExpiration); + + return Jwts.builder() + .setHeader(Map.of("alg", "HS256", "typ", "JWT")) + .setSubject(userDetails.getUsername()) + .issuedAt(Date.from(issuedAt)) + .expiration(Date.from(expiredAt)) + .signWith(secret, SignatureAlgorithm.HS256) + .compact(); + } + + // RefreshToken 생성 + public String createRefreshToken(PrincipalDetails userDetails) { + Instant issuedAt = Instant.now(); + Instant expiredAt = issuedAt.plusMillis(refreshExpiration); + + String refreshToken = Jwts.builder() + .setHeader(Map.of("alg", "HS256", "typ", "JWT")) + .setSubject(userDetails.getUsername()) + .issuedAt(Date.from(issuedAt)) + .expiration(Date.from(expiredAt)) + .signWith(secret, SignatureAlgorithm.HS256) + .compact(); + redisUtil.set(userDetails.getUsername(), refreshToken); + redisUtil.expire(userDetails.getUsername(), refreshExpiration, TimeUnit.MILLISECONDS); + return refreshToken; + } + + // 헤더에서 토큰 추출 + public String resolveAccessToken(HttpServletRequest request) { + String header = request.getHeader("Authorization"); + if (header == null || !header.startsWith("Bearer ")) { + return null; + } + return header.split(" ")[1]; + } + + // AccessToken 유효성 확인 + public boolean validateToken(String token) { + try { + Jws claims = getClaims(token); + return claims.getBody().getExpiration().after(Date.from(Instant.now())); + } catch (JwtException e) { + log.error(e.getMessage()); + return false; + } catch (Exception e) { + log.error(e.getMessage() + ": 토큰이 유효하지 않습니다."); + return false; + } + } + + // RefreshToken 유효성 확인 + public void validateRefreshToken(String refreshToken) { + String username = getUserId(refreshToken); + + //redis 확인 + if (!redisUtil.exists(username)) { + throw new AuthHandler(ErrorStatus.INVALID_TOKEN); + } + } + + //userId 추출 + public String getUserId(String token) { + return getClaims(token).getBody().getSubject(); + } + + //토큰의 클레임 가져오는 메서드 + public Jws getClaims(String token) { + try { + return Jwts.parser() + .setSigningKey(secret) + .build() + .parseClaimsJws(token); + } catch (Exception e) { + throw new AuthHandler(ErrorStatus.INVALID_TOKEN); + } + } + + // 토큰 재발급 + public JwtDTO reissueToken(String refreshToken) throws SignatureException { + UserDetails userDetails = userDetailsService.loadUserByUsername(getUserId(refreshToken)); + + return new JwtDTO( + createAccessToken((PrincipalDetails) userDetails), + createRefreshToken((PrincipalDetails)userDetails) + ); + } + + // 토큰 유효시간 반환 + public Long getExpTime(String token) { + return getClaims(token).getPayload().getExpiration().getTime(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java index 33c92e83..ca017952 100644 --- a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java +++ b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java @@ -3,7 +3,10 @@ import com.otakumap.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface UserRepository extends JpaRepository { + Optional findByUserId(String userId); boolean existsByNickname(String nickname); boolean existsByUserId(String userId); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java new file mode 100644 index 00000000..4f95b7de --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.user.service; + +import com.otakumap.domain.user.entity.User; + +public interface UserQueryService { + User getUserByUserId(String userId); +} diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java new file mode 100644 index 00000000..d1cbbf32 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -0,0 +1,19 @@ +package com.otakumap.domain.user.service; + +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserQueryServiceImpl implements UserQueryService { + private final UserRepository userRepository; + + @Override + public User getUserByUserId(String userId) { + return userRepository.findByUserId(userId).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 89e44619..a9c9e2d7 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -21,6 +21,9 @@ public enum ErrorStatus implements BaseErrorCode { EMAIL_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "AUTH4002", "인증 코드가 만료되었습니다. 다시 요청해주세요."), CODE_NOT_EQUAL(HttpStatus.BAD_REQUEST, "AUTH4003", "인증 코드가 올바르지 않습니다."), EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AUTH5001", "메일 발송 중 오류가 발생했습니다."), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH4004", "유효하지 않은 토큰입니다."), + TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH4005", "토큰이 만료되었습니다."), + TOKEN_LOGGED_OUT(HttpStatus.UNAUTHORIZED, "AUTH4006", "이 토큰은 로그아웃되어 더 이상 유효하지 않습니다."), // 멤버 관련 에러 USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 2b1e0978..0230982b 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -5,13 +5,13 @@ import com.otakumap.domain.auth.jwt.handler.JwtAuthenticationEntryPoint; import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetailsService; import com.otakumap.domain.auth.jwt.util.JwtProvider; +import com.otakumap.global.util.RedisUtil; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @@ -24,6 +24,7 @@ public class SecurityConfig { private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final JwtProvider jwtProvider; + private final RedisUtil redisUtil; private final PrincipalDetailsService principalDetailsService; private final String[] allowUrl = { @@ -47,7 +48,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // BasicHttp 비활성화 .httpBasic(AbstractHttpConfigurer::disable) //JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가 - .addFilterBefore(new JwtFilter(jwtProvider, principalDetailsService), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtFilter(jwtProvider, redisUtil, principalDetailsService), UsernamePasswordAuthenticationFilter.class) // 예외 처리 설정 .exceptionHandling(exception -> exception .authenticationEntryPoint(jwtAuthenticationEntryPoint) diff --git a/src/main/java/com/otakumap/global/util/RedisUtil.java b/src/main/java/com/otakumap/global/util/RedisUtil.java index 6bad0eee..a24744ec 100644 --- a/src/main/java/com/otakumap/global/util/RedisUtil.java +++ b/src/main/java/com/otakumap/global/util/RedisUtil.java @@ -7,29 +7,32 @@ import org.springframework.stereotype.Service; import java.time.Duration; +import java.util.concurrent.TimeUnit; @RequiredArgsConstructor @Service public class RedisUtil { private final RedisTemplate redisTemplate; - private final StringRedisTemplate stringRedisTemplate; - public String getData(String key) { - ValueOperations valueOperations = stringRedisTemplate.opsForValue(); - return valueOperations.get(key); + public void set(String key, Object value) { + redisTemplate.opsForValue().set(key, value); } - public boolean existData(String key) { - return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); + public Object get(String key) { + return redisTemplate.opsForValue().get(key); } - public void setDataExpire(String key, String value, long duration) { - ValueOperations valueOperations = stringRedisTemplate.opsForValue(); - Duration expireDuration = Duration.ofSeconds(duration); - valueOperations.set(key, value, expireDuration); + public boolean exists(String key) { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } - public void deleteData(String key) { - stringRedisTemplate.delete(key); + public void expire(String key, long timeout, TimeUnit unit) { + redisTemplate.expire(key, timeout, unit); } + + + public boolean delete(String key) { + return Boolean.TRUE.equals(redisTemplate.delete(key)); + } + } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bbc7b0ea..8321bc7e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -33,9 +33,16 @@ spring: timeout: 5000 writetimeout: 5000 auth-code-expiration-millis: 1800000 # 30 * 60 * 1000 == 30? + data: redis: host: localhost port: 6379 repositories: - enabled: false \ No newline at end of file + enabled: false + + jwt: + secret: ${JWT_SECRET} + token: + access-expiration-time: 3600000 #1시간 + refresh-expiration-time: 604800000 #7일 \ No newline at end of file From d8fb504a39367f899dd48f98b55d51d7b34d5a2c Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 23:15:44 +0900 Subject: [PATCH 054/516] =?UTF-8?q?Feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83,=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 26 +++++++-- .../domain/auth/dto/AuthRequestDTO.java | 8 +++ .../domain/auth/dto/AuthResponseDTO.java | 12 +++- .../auth/service/AuthCommandService.java | 6 ++ .../auth/service/AuthCommandServiceImpl.java | 55 ++++++++++++++++++- .../domain/user/converter/UserConverter.java | 9 +++ 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index 13129000..45fd86ed 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -2,17 +2,16 @@ import com.otakumap.domain.auth.dto.AuthRequestDTO; import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.auth.jwt.dto.JwtDTO; import com.otakumap.domain.auth.service.AuthCommandService; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -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; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") @@ -26,6 +25,12 @@ public ApiResponse signup(@RequestBody @Valid A return ApiResponse.onSuccess(UserConverter.toSignupResultDTO(authCommandService.signup(request))); } + @Operation(summary = "일반 로그인", description = "일반 로그인 기능입니다.") + @PostMapping("/login") + public ApiResponse login(@RequestBody @Valid AuthRequestDTO.LoginDTO request) { + return ApiResponse.onSuccess(authCommandService.login(request)); + } + @Operation(summary = "닉네임 중복 확인", description = "닉네임 중복 확인 기능입니다.") @PostMapping("/check-nickname") public ApiResponse checkNickname(@RequestBody @Valid AuthRequestDTO.CheckNicknameDTO request) { @@ -49,4 +54,17 @@ public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.Verify public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyCodeDTO request) { return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request))); } + + @Operation(summary = "토큰 재발급", description = "accessToken이 만료 시 refreshToken을 통해 accessToken을 재발급합니다.") + @PostMapping("/reissue") + public ApiResponse reissueToken(@RequestHeader("RefreshToken") String refreshToken) { + return ApiResponse.onSuccess(authCommandService.reissueToken(refreshToken)); + } + + @Operation(summary = "로그아웃", description = "로그아웃 기능입니다.") + @PostMapping("/logout") + public ApiResponse logout(HttpServletRequest request) { + authCommandService.logout(request); + return ApiResponse.onSuccess("로그아웃 되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index a1bcbb56..2b0b83ba 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -37,6 +37,14 @@ public static class SignupDTO { String passwordCheck; } + @Getter + public static class LoginDTO { + @NotNull + String userId; + @NotNull + String password; + } + @Getter public static class CheckNicknameDTO { @NotNull diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java index 136a8b44..b1935af0 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java @@ -8,7 +8,6 @@ import java.time.LocalDateTime; public class AuthResponseDTO { - @Getter @AllArgsConstructor @NoArgsConstructor @@ -18,6 +17,17 @@ public static class SignupResultDTO { LocalDateTime createdAt; } + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class LoginResultDTO { + Long id; + String accessToken; + String refreshToken; + } + + @Getter @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java index 913854e9..d6451ad9 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -1,13 +1,19 @@ package com.otakumap.domain.auth.service; import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.auth.jwt.dto.JwtDTO; import com.otakumap.domain.user.entity.User; import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; public interface AuthCommandService { User signup(AuthRequestDTO.SignupDTO request); + AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request); boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request); boolean checkId(AuthRequestDTO.CheckIdDTO request); String verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); + JwtDTO reissueToken(String refreshToken); + void logout(HttpServletRequest request); } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index 6bc68a98..7657177d 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -1,18 +1,26 @@ package com.otakumap.domain.auth.service; import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.auth.jwt.dto.JwtDTO; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; +import com.otakumap.domain.auth.jwt.util.JwtProvider; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.AuthHandler; import com.otakumap.global.util.RedisUtil; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.mail.MailException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.concurrent.TimeUnit; + @Service @RequiredArgsConstructor public class AuthCommandServiceImpl implements AuthCommandService { @@ -20,6 +28,7 @@ public class AuthCommandServiceImpl implements AuthCommandService { private final PasswordEncoder passwordEncoder; private final RedisUtil redisUtil; private final MailService mailService; + private final JwtProvider jwtProvider; @Override public User signup(AuthRequestDTO.SignupDTO request) { @@ -31,9 +40,25 @@ public User signup(AuthRequestDTO.SignupDTO request) { return userRepository.save(newUser); } + @Override + public AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request) { + User user = userRepository.findByUserId(request.getUserId()).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + + if(!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new AuthHandler(ErrorStatus.PASSWORD_NOT_EQUAL); + } + + PrincipalDetails memberDetails = new PrincipalDetails(user); + + // 로그인 성공 시 토큰 생성 + String accessToken = jwtProvider.createAccessToken(memberDetails); + String refreshToken = jwtProvider.createRefreshToken(memberDetails); + + return UserConverter.toLoginResultDTO(user, accessToken, refreshToken); + } + @Override public boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request) { - System.out.println(request.getNickname()); return userRepository.existsByNickname(request.getNickname()); } @@ -54,7 +79,7 @@ public String verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws Messagin @Override public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request) { - String authCode = redisUtil.getData(request.getEmail()); + String authCode = (String) redisUtil.get(request.getEmail()); if (authCode == null) { throw new AuthHandler(ErrorStatus.EMAIL_CODE_EXPIRED); } @@ -63,4 +88,30 @@ public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request) { } return true; } + + @Override + public JwtDTO reissueToken(String refreshToken) { + try { + jwtProvider.validateRefreshToken(refreshToken); + return jwtProvider.reissueToken(refreshToken); + } catch (ExpiredJwtException eje) { + throw new AuthHandler(ErrorStatus.TOKEN_EXPIRED); + } catch (IllegalArgumentException iae) { + throw new AuthHandler(ErrorStatus.INVALID_TOKEN); + } + } + + @Override + public void logout(HttpServletRequest request) { + try { + String accessToken = jwtProvider.resolveAccessToken(request); + // 블랙리스트에 저장 + redisUtil.set(accessToken, "logout"); + redisUtil.expire(accessToken, jwtProvider.getExpTime(accessToken), TimeUnit.MILLISECONDS); + // RefreshToken 삭제 + redisUtil.delete(jwtProvider.getUserId(accessToken)); + } catch (ExpiredJwtException e) { + throw new AuthHandler(ErrorStatus.TOKEN_EXPIRED); + } + } } diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index c086c53c..a5a1431f 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -28,6 +28,15 @@ public static AuthResponseDTO.SignupResultDTO toSignupResultDTO(User user) { .build(); } + public static AuthResponseDTO.LoginResultDTO toLoginResultDTO(User user, String accessToken, String refreshToken) { + return AuthResponseDTO.LoginResultDTO.builder() + .id(user.getId()) + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + + } + public static AuthResponseDTO.CheckNicknameResultDTO toCheckNicknameResultDTO(boolean isDuplicated) { return AuthResponseDTO.CheckNicknameResultDTO.builder() .isDuplicated(isDuplicated) From f95c278e439890f343d3e139fb0ccebff212ca93 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 23:38:43 +0900 Subject: [PATCH 055/516] =?UTF-8?q?Chore:=20=EB=A0=88=EB=94=94=EC=8A=A4=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8321bc7e..65bf9098 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,8 +36,8 @@ spring: data: redis: - host: localhost - port: 6379 + host: ${REDIS_HOST} + port: ${REDIS_PORT} repositories: enabled: false From 24e3cecaab9d9785a8970418527496b3aed88b39 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 23:40:22 +0900 Subject: [PATCH 056/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B2=B4=ED=81=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/auth/controller/AuthController.java | 3 ++- .../otakumap/domain/auth/service/AuthCommandService.java | 2 +- .../domain/auth/service/AuthCommandServiceImpl.java | 6 ++++-- .../otakumap/domain/user/repository/UserRepository.java | 1 + .../global/apiPayload/code/status/ErrorStatus.java | 7 ++++--- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index 45fd86ed..ca2aa71c 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -46,7 +46,8 @@ public ApiResponse checkId(@RequestBody @Valid @Operation(summary = "이메일 인증 메일 전송", description = "이메일 인증을 위한 메일 전송 기능입니다.") @PostMapping("/verify-email") public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { - return ApiResponse.onSuccess(authCommandService.verifyEmail(request)); + authCommandService.verifyEmail(request); + return ApiResponse.onSuccess("이메일 인증이 성공적으로 완료되었습니다."); } @Operation(summary = "이메일 코드 인증", description = "이메일 코드 인증 기능입니다.") diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java index d6451ad9..181eea5d 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -12,7 +12,7 @@ public interface AuthCommandService { AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request); boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request); boolean checkId(AuthRequestDTO.CheckIdDTO request); - String verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; + void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); JwtDTO reissueToken(String refreshToken); void logout(HttpServletRequest request); diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index 7657177d..236c898a 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -68,13 +68,15 @@ public boolean checkId(AuthRequestDTO.CheckIdDTO request) { } @Override - public String verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { + public void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { try { + if(userRepository.existsByEmail(request.getEmail())) { + throw new AuthHandler(ErrorStatus.EMAIL_ALREADY_EXISTS); + } mailService.sendEmail(request.getEmail()); } catch (MailException e) { throw new AuthHandler(ErrorStatus.EMAIL_SEND_FAILED); } - return "이메일 인증이 성공적으로 완료되었습니다."; } @Override diff --git a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java index ca017952..62a3b1dd 100644 --- a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java +++ b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java @@ -9,4 +9,5 @@ public interface UserRepository extends JpaRepository { Optional findByUserId(String userId); boolean existsByNickname(String nickname); boolean existsByUserId(String userId); + boolean existsByEmail(String email); } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index a9c9e2d7..d7f9f59f 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -21,9 +21,10 @@ public enum ErrorStatus implements BaseErrorCode { EMAIL_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "AUTH4002", "인증 코드가 만료되었습니다. 다시 요청해주세요."), CODE_NOT_EQUAL(HttpStatus.BAD_REQUEST, "AUTH4003", "인증 코드가 올바르지 않습니다."), EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AUTH5001", "메일 발송 중 오류가 발생했습니다."), - INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH4004", "유효하지 않은 토큰입니다."), - TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH4005", "토큰이 만료되었습니다."), - TOKEN_LOGGED_OUT(HttpStatus.UNAUTHORIZED, "AUTH4006", "이 토큰은 로그아웃되어 더 이상 유효하지 않습니다."), + EMAIL_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "AUTH4004", "이미 사용 중인 이메일입니다."), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH4005", "유효하지 않은 토큰입니다."), + TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH4006", "토큰이 만료되었습니다."), + TOKEN_LOGGED_OUT(HttpStatus.UNAUTHORIZED, "AUTH4007", "이 토큰은 로그아웃되어 더 이상 유효하지 않습니다."), // 멤버 관련 에러 USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), From 8c42b71c99f426cb7a668cf2592baea4b7141c39 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 16 Jan 2025 23:41:53 +0900 Subject: [PATCH 057/516] =?UTF-8?q?Feat:=20=EB=A0=88=EB=94=94=EC=8A=A4=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=80=EC=9E=A5=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/auth/service/MailService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/service/MailService.java b/src/main/java/com/otakumap/domain/auth/service/MailService.java index fb01a3b2..9db955d4 100644 --- a/src/main/java/com/otakumap/domain/auth/service/MailService.java +++ b/src/main/java/com/otakumap/domain/auth/service/MailService.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Service; import java.util.Random; +import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor @@ -52,8 +53,9 @@ private MimeMessage createEmailForm(String email, String code) throws MessagingE body += "

인증번호 " + code + "

"; message.setText(body, "UTF-8", "html"); - // Redis 에 해당 인증코드 인증 시간 설정(30분) - redisUtil.setDataExpire(email, code, 60 * 30L); + // Redis에 해당 인증코드 인증 시간 설정(30분) + redisUtil.set(email, code); + redisUtil.expire(email, 30 * 60 * 1000L, TimeUnit.MILLISECONDS); return message; } @@ -61,8 +63,8 @@ private MimeMessage createEmailForm(String email, String code) throws MessagingE // 메일 발송 @Async public void sendEmail(String sendEmail) throws MessagingException { - if (redisUtil.existData(sendEmail)) { - redisUtil.deleteData(sendEmail); + if (redisUtil.exists(sendEmail)) { + redisUtil.delete(sendEmail); } String authCode = createCode(); MimeMessage message = createEmailForm(sendEmail, authCode); // 메일 생성 From 9ebd86b87535b4a4a4979afd7e59aa420a2994be Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 17 Jan 2025 11:02:15 +0900 Subject: [PATCH 058/516] =?UTF-8?q?Feat:=20JWT=20=ED=86=A0=ED=81=B0=20payl?= =?UTF-8?q?oad=EC=97=90=20User=20PK,=20email=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/jwt/filter/JwtFilter.java | 5 ++-- ...Resolver.java => CurrentUserResolver.java} | 4 +-- .../jwt/userdetails/PrincipalDetails.java | 2 +- .../userdetails/PrincipalDetailsService.java | 4 +-- .../domain/auth/jwt/util/JwtProvider.java | 28 ++++++++++++------- .../auth/service/AuthCommandServiceImpl.java | 6 ++-- .../user/repository/UserRepository.java | 1 + .../domain/user/service/UserQueryService.java | 2 +- .../user/service/UserQueryServiceImpl.java | 4 +-- .../com/otakumap/global/config/WebConfig.java | 4 +-- 10 files changed, 34 insertions(+), 26 deletions(-) rename src/main/java/com/otakumap/domain/auth/jwt/resolver/{AuthenticatedUserResolver.java => CurrentUserResolver.java} (90%) diff --git a/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java b/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java index 259315dc..b316c587 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/filter/JwtFilter.java @@ -40,14 +40,13 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht if (blackListValue != null && blackListValue.equals("logout")) { throw new AuthHandler(ErrorStatus.TOKEN_LOGGED_OUT); } - String userId = jwtProvider.getUserId(accessToken); + String email = jwtProvider.getEmail(accessToken); //유저와 토큰 일치 시 userDetails 생성 - UserDetails userDetails = principalDetailsService.loadUserByUsername(userId); + UserDetails userDetails = principalDetailsService.loadUserByUsername(email); if (userDetails != null) { //userDetails, password, role -> 접근 권한 인증 Token 생성 Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); - //현재 Request의 Security Context에 접근 권한 설정 SecurityContextHolder.getContext().setAuthentication(authentication); } else { diff --git a/src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java b/src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java similarity index 90% rename from src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java rename to src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java index 72b32379..dce0c0b5 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/resolver/AuthenticatedUserResolver.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java @@ -18,7 +18,7 @@ @Component @RequiredArgsConstructor @Slf4j -public class AuthenticatedUserResolver implements HandlerMethodArgumentResolver { +public class CurrentUserResolver implements HandlerMethodArgumentResolver { private final UserQueryService userQueryService; @Override @@ -32,7 +32,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m if (authentication != null) { PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); - return userQueryService.getUserByUserId(principalDetails.getUsername()); + return userQueryService.getUserByEmail(principalDetails.getUsername()); } return null; } diff --git a/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java index a36497b0..0a3f3715 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetails.java @@ -27,7 +27,7 @@ public Collection getAuthorities() { @Override public String getUsername() { - return user.getUserId(); + return user.getEmail(); } @Override diff --git a/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java index a428ac8d..af4acfad 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/userdetails/PrincipalDetailsService.java @@ -16,8 +16,8 @@ public class PrincipalDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { - User user = userRepository.findByUserId(userId).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + User user = userRepository.findByEmail(email).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); return new PrincipalDetails(user); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java b/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java index a7f3970d..b82791ee 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/util/JwtProvider.java @@ -3,6 +3,7 @@ import com.otakumap.domain.auth.jwt.dto.JwtDTO; import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetailsService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.AuthHandler; import com.otakumap.global.util.RedisUtil; @@ -45,13 +46,14 @@ public JwtProvider( } // AccessToken 생성 - public String createAccessToken(PrincipalDetails userDetails) { + public String createAccessToken(PrincipalDetails userDetails, Long userId) { Instant issuedAt = Instant.now(); Instant expiredAt = issuedAt.plusMillis(accessExpiration); return Jwts.builder() .setHeader(Map.of("alg", "HS256", "typ", "JWT")) - .setSubject(userDetails.getUsername()) + .setSubject(userDetails.getUsername()) // 이메일 + .claim("id", userId) .issuedAt(Date.from(issuedAt)) .expiration(Date.from(expiredAt)) .signWith(secret, SignatureAlgorithm.HS256) @@ -59,17 +61,19 @@ public String createAccessToken(PrincipalDetails userDetails) { } // RefreshToken 생성 - public String createRefreshToken(PrincipalDetails userDetails) { + public String createRefreshToken(PrincipalDetails userDetails, Long userId) { Instant issuedAt = Instant.now(); Instant expiredAt = issuedAt.plusMillis(refreshExpiration); String refreshToken = Jwts.builder() .setHeader(Map.of("alg", "HS256", "typ", "JWT")) - .setSubject(userDetails.getUsername()) + .setSubject(userDetails.getUsername()) // 이메일 + .claim("id", userId) .issuedAt(Date.from(issuedAt)) .expiration(Date.from(expiredAt)) .signWith(secret, SignatureAlgorithm.HS256) .compact(); + redisUtil.set(userDetails.getUsername(), refreshToken); redisUtil.expire(userDetails.getUsername(), refreshExpiration, TimeUnit.MILLISECONDS); return refreshToken; @@ -100,7 +104,7 @@ public boolean validateToken(String token) { // RefreshToken 유효성 확인 public void validateRefreshToken(String refreshToken) { - String username = getUserId(refreshToken); + String username = getEmail(refreshToken); //redis 확인 if (!redisUtil.exists(username)) { @@ -108,11 +112,14 @@ public void validateRefreshToken(String refreshToken) { } } - //userId 추출 - public String getUserId(String token) { + //email 추출 + public String getEmail(String token) { return getClaims(token).getBody().getSubject(); } + //id(PK) 추출 + public Long getId(String token) { return getClaims(token).getBody().get("id", Long.class); } + //토큰의 클레임 가져오는 메서드 public Jws getClaims(String token) { try { @@ -127,11 +134,12 @@ public Jws getClaims(String token) { // 토큰 재발급 public JwtDTO reissueToken(String refreshToken) throws SignatureException { - UserDetails userDetails = userDetailsService.loadUserByUsername(getUserId(refreshToken)); + UserDetails userDetails = userDetailsService.loadUserByUsername(getEmail(refreshToken)); + Long userId = getId(refreshToken); return new JwtDTO( - createAccessToken((PrincipalDetails) userDetails), - createRefreshToken((PrincipalDetails)userDetails) + createAccessToken((PrincipalDetails) userDetails, userId), + createRefreshToken((PrincipalDetails)userDetails, userId) ); } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index 236c898a..bf95f783 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -51,8 +51,8 @@ public AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request) { PrincipalDetails memberDetails = new PrincipalDetails(user); // 로그인 성공 시 토큰 생성 - String accessToken = jwtProvider.createAccessToken(memberDetails); - String refreshToken = jwtProvider.createRefreshToken(memberDetails); + String accessToken = jwtProvider.createAccessToken(memberDetails, user.getId()); + String refreshToken = jwtProvider.createRefreshToken(memberDetails, user.getId()); return UserConverter.toLoginResultDTO(user, accessToken, refreshToken); } @@ -111,7 +111,7 @@ public void logout(HttpServletRequest request) { redisUtil.set(accessToken, "logout"); redisUtil.expire(accessToken, jwtProvider.getExpTime(accessToken), TimeUnit.MILLISECONDS); // RefreshToken 삭제 - redisUtil.delete(jwtProvider.getUserId(accessToken)); + redisUtil.delete(jwtProvider.getEmail(accessToken)); } catch (ExpiredJwtException e) { throw new AuthHandler(ErrorStatus.TOKEN_EXPIRED); } diff --git a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java index 62a3b1dd..d43fcf32 100644 --- a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java +++ b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java @@ -7,6 +7,7 @@ public interface UserRepository extends JpaRepository { Optional findByUserId(String userId); + Optional findByEmail(String email); boolean existsByNickname(String nickname); boolean existsByUserId(String userId); boolean existsByEmail(String email); diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java index 4f95b7de..2e9c0fba 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java @@ -3,5 +3,5 @@ import com.otakumap.domain.user.entity.User; public interface UserQueryService { - User getUserByUserId(String userId); + User getUserByEmail(String email); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index d1cbbf32..d3ac4557 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -13,7 +13,7 @@ public class UserQueryServiceImpl implements UserQueryService { private final UserRepository userRepository; @Override - public User getUserByUserId(String userId) { - return userRepository.findByUserId(userId).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + public User getUserByEmail(String email) { + return userRepository.findByEmail(email).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); } } diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java index ab0bc469..1e20017c 100644 --- a/src/main/java/com/otakumap/global/config/WebConfig.java +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -1,6 +1,6 @@ package com.otakumap.global.config; -import com.otakumap.domain.auth.jwt.resolver.AuthenticatedUserResolver; +import com.otakumap.domain.auth.jwt.resolver.CurrentUserResolver; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -11,7 +11,7 @@ @Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { - private final AuthenticatedUserResolver authenticatedUserResolver; + private final CurrentUserResolver authenticatedUserResolver; @Override public void addArgumentResolvers(List resolvers) { From 48d9390e582f81f42b8224e344a004954206595e Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 17 Jan 2025 11:21:22 +0900 Subject: [PATCH 059/516] =?UTF-8?q?Fix:=20Redis=EC=9D=98=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=ED=82=A4=20=EC=A4=91=EB=B3=B5=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthCommandServiceImpl.java | 2 +- .../com/otakumap/domain/auth/service/MailService.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index bf95f783..5ca06fe3 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -81,7 +81,7 @@ public void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingE @Override public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request) { - String authCode = (String) redisUtil.get(request.getEmail()); + String authCode = (String) redisUtil.get("auth:" + request.getEmail()); if (authCode == null) { throw new AuthHandler(ErrorStatus.EMAIL_CODE_EXPIRED); } diff --git a/src/main/java/com/otakumap/domain/auth/service/MailService.java b/src/main/java/com/otakumap/domain/auth/service/MailService.java index 9db955d4..fbf32301 100644 --- a/src/main/java/com/otakumap/domain/auth/service/MailService.java +++ b/src/main/java/com/otakumap/domain/auth/service/MailService.java @@ -54,8 +54,8 @@ private MimeMessage createEmailForm(String email, String code) throws MessagingE message.setText(body, "UTF-8", "html"); // Redis에 해당 인증코드 인증 시간 설정(30분) - redisUtil.set(email, code); - redisUtil.expire(email, 30 * 60 * 1000L, TimeUnit.MILLISECONDS); + redisUtil.set("auth:" + email, code); + redisUtil.expire("auth:" + email, 30 * 60 * 1000L, TimeUnit.MILLISECONDS); return message; } @@ -63,8 +63,8 @@ private MimeMessage createEmailForm(String email, String code) throws MessagingE // 메일 발송 @Async public void sendEmail(String sendEmail) throws MessagingException { - if (redisUtil.exists(sendEmail)) { - redisUtil.delete(sendEmail); + if (redisUtil.exists("auth:" + sendEmail)) { + redisUtil.delete("auth:" + sendEmail); } String authCode = createCode(); MimeMessage message = createEmailForm(sendEmail, authCode); // 메일 생성 From 2cac2dfb8e1976f235f00dfcd52cf4141310c8a7 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Fri, 17 Jan 2025 15:08:23 +0900 Subject: [PATCH 060/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventReviewController.java | 35 +++++++++++++++ .../converter/EventReviewConverter.java | 44 +++++++++++++++++++ .../dto/EventReviewResponseDTO.java | 33 ++++++++++++++ .../event_review/entity/EventReview.java | 43 ++++++++++++++++++ .../repository/EventReviewRepository.java | 11 +++++ .../service/EventReviewCommandServivce.java | 8 ++++ .../EventReviewCommandServivceImpl.java | 26 +++++++++++ 7 files changed, 200 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java create mode 100644 src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java create mode 100644 src/main/java/com/otakumap/domain/event_review/dto/EventReviewResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/event_review/entity/EventReview.java create mode 100644 src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java create mode 100644 src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java diff --git a/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java b/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java new file mode 100644 index 00000000..6000db42 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java @@ -0,0 +1,35 @@ +package com.otakumap.domain.event_review.controller; + + +import com.otakumap.domain.event_review.converter.EventReviewConverter; +import com.otakumap.domain.event_review.dto.EventReviewResponseDTO; +import com.otakumap.domain.event_review.service.EventReviewCommandServivce; +import com.otakumap.global.apiPayload.ApiResponse; +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.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/api") +public class EventReviewController { + + private final EventReviewCommandServivce eventReviewCommandServivce; + + @GetMapping("/events/{eventId}/reviews") + @Operation(summary = "특정 이벤트의 후기 목록 조회", description = "특정 이벤트의 후기 목록(4개씩)을 불러옵니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "eventId", description = "이벤트의 아이디입니다.") + }) + public ApiResponse getEventReviewList(@PathVariable(name = "eventId") Long eventId, @RequestParam(name = "page") Integer page) { + return ApiResponse.onSuccess(EventReviewConverter.eventReviewPreViewListDTO(eventReviewCommandServivce.getEventReviews(eventId, page))); + } +} diff --git a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java new file mode 100644 index 00000000..34a9c85b --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java @@ -0,0 +1,44 @@ +package com.otakumap.domain.event_review.converter; + +import com.otakumap.domain.event_review.dto.EventReviewResponseDTO; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + +public class EventReviewConverter { + + public static EventReviewResponseDTO.EventReviewPreViewDTO eventReviewPreViewDTO(EventReview eventReview) { + ImageResponseDTO.ImageDTO image = ImageResponseDTO.ImageDTO.builder() + .id(eventReview.getImage().getId()) + .uuid(eventReview.getImage().getUuid()) + .fileUrl(eventReview.getImage().getFileUrl()) + .fileName(eventReview.getImage().getFileName()) + .build(); + + return EventReviewResponseDTO.EventReviewPreViewDTO.builder() + .id(eventReview.getId()) + .title(eventReview.getTitle()) + .reviewPhotoUrl(image) + .build(); + } + + public static EventReviewResponseDTO.EventReviewPreViewListDTO eventReviewPreViewListDTO(Page eventReviewList) { + if(eventReviewList == null || eventReviewList.isEmpty()) return null; + + List eventReviewDTOList = eventReviewList.stream() + .map(EventReviewConverter::eventReviewPreViewDTO).collect(Collectors.toList()); + + return EventReviewResponseDTO.EventReviewPreViewListDTO.builder() + .eventReviews(eventReviewDTOList) + .totalPages(eventReviewList.getTotalPages()) + .totalElements(eventReviewList.getNumber()) + .isFirst(eventReviewList.isFirst()) + .isLast(eventReviewList.isLast()) + .build(); + + + } +} diff --git a/src/main/java/com/otakumap/domain/event_review/dto/EventReviewResponseDTO.java b/src/main/java/com/otakumap/domain/event_review/dto/EventReviewResponseDTO.java new file mode 100644 index 00000000..5f41e45c --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/dto/EventReviewResponseDTO.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.event_review.dto; + +import com.otakumap.domain.image.dto.ImageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class EventReviewResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventReviewPreViewDTO { + Long id; + String title; + ImageResponseDTO.ImageDTO reviewPhotoUrl; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventReviewPreViewListDTO { + List eventReviews; + Integer totalPages; + Integer totalElements; + Boolean isFirst; + Boolean isLast; + } +} diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java new file mode 100644 index 00000000..7a8955c2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -0,0 +1,43 @@ +package com.otakumap.domain.event_review.entity; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventReview extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + + private String content; + + private Integer view; + + private Float rating; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "image_id", referencedColumnName = "id") + private Image image; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id") + private Event event; +} diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java new file mode 100644 index 00000000..605451cf --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.event_review.repository; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event_review.entity.EventReview; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventReviewRepository extends JpaRepository { + Page findAllByEvent(Event event, PageRequest pageRequest); +} diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java new file mode 100644 index 00000000..05963ae4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.event_review.service; + +import com.otakumap.domain.event_review.entity.EventReview; +import org.springframework.data.domain.Page; + +public interface EventReviewCommandServivce { + Page getEventReviews(Long eventId, Integer page); +} diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java new file mode 100644 index 00000000..be90dd8a --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java @@ -0,0 +1,26 @@ +package com.otakumap.domain.event_review.service; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EventReviewCommandServivceImpl implements EventReviewCommandServivce { + + private final EventRepository eventRepository; + private final EventReviewRepository eventReviewRepository; + + @Override + public Page getEventReviews(Long eventId, Integer page) { + Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + return eventReviewRepository.findAllByEvent(event, PageRequest.of(page, 4)); + } +} From b0d1d13c9632681c707dd1cf591a839ccf996d22 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 18 Jan 2025 00:31:26 +0900 Subject: [PATCH 061/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20entity=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/event_review/entity/EventReview.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 7a8955c2..d74ac552 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -21,12 +21,16 @@ public class EventReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(length = 20, nullable = false) private String title; + @Column(columnDefinition = "text not null") private String content; + @Column(columnDefinition = "bigint default 0 not null") private Integer view; + @Column(nullable = false) private Float rating; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) From ceed3ba13939aae83c148a196dd22262acc6bcf2 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 18 Jan 2025 03:24:03 +0900 Subject: [PATCH 062/516] =?UTF-8?q?Feat:=20QueryDSL=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 29 ++++++ .../otakumap/domain/event/entity/QEvent.java | 88 +++++++++++++++++++ .../eventLocation/entity/QEventLocation.java | 65 ++++++++++++++ .../entity/QEventShortReview.java | 70 +++++++++++++++ .../domain/event_like/entity/QEventLike.java | 64 ++++++++++++++ .../event_review/entity/QEventReview.java | 73 +++++++++++++++ .../otakumap/domain/image/entity/QImage.java | 51 +++++++++++ .../otakumap/domain/place/entity/QPlace.java | 52 +++++++++++ .../domain/place_like/entity/QPlaceLike.java | 64 ++++++++++++++ .../place_review/entity/QPlaceReview.java | 68 ++++++++++++++ .../entity/QPlaceShortReview.java | 70 +++++++++++++++ .../otakumap/domain/user/entity/QUser.java | 77 ++++++++++++++++ .../otakumap/global/common/QBaseEntity.java | 39 ++++++++ .../reviews/service/reviewsService.java | 4 + .../reviews/service/reviewsServiceImpl.java | 4 + .../global/config/QueryDSLConfig.java | 18 ++++ 16 files changed, 836 insertions(+) create mode 100644 src/main/generated/com/otakumap/domain/event/entity/QEvent.java create mode 100644 src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java create mode 100644 src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java create mode 100644 src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java create mode 100644 src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java create mode 100644 src/main/generated/com/otakumap/domain/image/entity/QImage.java create mode 100644 src/main/generated/com/otakumap/domain/place/entity/QPlace.java create mode 100644 src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java create mode 100644 src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java create mode 100644 src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java create mode 100644 src/main/generated/com/otakumap/domain/user/entity/QUser.java create mode 100644 src/main/generated/com/otakumap/global/common/QBaseEntity.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/reviewsService.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/config/QueryDSLConfig.java diff --git a/build.gradle b/build.gradle index 546d2140..3724ffe8 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.4.1' id 'io.spring.dependency-management' version '1.1.7' + id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" } group = 'com' @@ -49,8 +50,36 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + + // queryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { useJUnitPlatform() } + +// Querydsl 설정부 +def generated = 'src/main/generated' + +querydsl { + jpa = true + querydslSourcesDir = generated +} +sourceSets { + main.java.srcDir generated +} + +compileQuerydsl{ + options.annotationProcessorPath = configurations.querydsl +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } + querydsl.extendsFrom compileClasspath +} diff --git a/src/main/generated/com/otakumap/domain/event/entity/QEvent.java b/src/main/generated/com/otakumap/domain/event/entity/QEvent.java new file mode 100644 index 00000000..3a7ba9c0 --- /dev/null +++ b/src/main/generated/com/otakumap/domain/event/entity/QEvent.java @@ -0,0 +1,88 @@ +package com.otakumap.domain.event.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QEvent is a Querydsl query type for Event + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QEvent extends EntityPathBase { + + private static final long serialVersionUID = 559538850L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QEvent event = new QEvent("event"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + public final StringPath animationName = createString("animationName"); + + public final com.otakumap.domain.image.entity.QImage backgroudImage; + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final DatePath endDate = createDate("endDate", java.time.LocalDate.class); + + public final ListPath eventLikeList = this.createList("eventLikeList", com.otakumap.domain.event_like.entity.EventLike.class, com.otakumap.domain.event_like.entity.QEventLike.class, PathInits.DIRECT2); + + public final com.otakumap.domain.eventLocation.entity.QEventLocation eventLocation; + + public final EnumPath genre = createEnum("genre", com.otakumap.domain.event.entity.enums.Genre.class); + + public final com.otakumap.domain.image.entity.QImage goodsImage; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final StringPath site = createString("site"); + + public final DatePath startDate = createDate("startDate", java.time.LocalDate.class); + + public final EnumPath status = createEnum("status", com.otakumap.domain.event.entity.enums.EventStatus.class); + + public final com.otakumap.domain.image.entity.QImage thumbnailImage; + + public final StringPath title = createString("title"); + + public final EnumPath type = createEnum("type", com.otakumap.domain.event.entity.enums.EventType.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QEvent(String variable) { + this(Event.class, forVariable(variable), INITS); + } + + public QEvent(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QEvent(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QEvent(PathMetadata metadata, PathInits inits) { + this(Event.class, metadata, inits); + } + + public QEvent(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.backgroudImage = inits.isInitialized("backgroudImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("backgroudImage")) : null; + this.eventLocation = inits.isInitialized("eventLocation") ? new com.otakumap.domain.eventLocation.entity.QEventLocation(forProperty("eventLocation"), inits.get("eventLocation")) : null; + this.goodsImage = inits.isInitialized("goodsImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("goodsImage")) : null; + this.thumbnailImage = inits.isInitialized("thumbnailImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("thumbnailImage")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java b/src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java new file mode 100644 index 00000000..9ee29238 --- /dev/null +++ b/src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java @@ -0,0 +1,65 @@ +package com.otakumap.domain.eventLocation.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QEventLocation is a Querydsl query type for EventLocation + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QEventLocation extends EntityPathBase { + + private static final long serialVersionUID = -609307966L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QEventLocation eventLocation = new QEventLocation("eventLocation"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final com.otakumap.domain.event.entity.QEvent event; + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath latitude = createString("latitude"); + + public final StringPath longitude = createString("longitude"); + + public final StringPath name = createString("name"); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QEventLocation(String variable) { + this(EventLocation.class, forVariable(variable), INITS); + } + + public QEventLocation(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QEventLocation(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QEventLocation(PathMetadata metadata, PathInits inits) { + this(EventLocation.class, metadata, inits); + } + + public QEventLocation(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java b/src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java new file mode 100644 index 00000000..710b2c6c --- /dev/null +++ b/src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java @@ -0,0 +1,70 @@ +package com.otakumap.domain.eventShortReview.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QEventShortReview is a Querydsl query type for EventShortReview + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QEventShortReview extends EntityPathBase { + + private static final long serialVersionUID = -1666552144L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QEventShortReview eventShortReview = new QEventShortReview("eventShortReview"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + public final StringPath content = createString("content"); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath dislikes = createNumber("dislikes", Integer.class); + + public final com.otakumap.domain.event.entity.QEvent event; + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath likes = createNumber("likes", Integer.class); + + public final NumberPath rating = createNumber("rating", Float.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final com.otakumap.domain.user.entity.QUser user; + + public QEventShortReview(String variable) { + this(EventShortReview.class, forVariable(variable), INITS); + } + + public QEventShortReview(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QEventShortReview(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QEventShortReview(PathMetadata metadata, PathInits inits) { + this(EventShortReview.class, metadata, inits); + } + + public QEventShortReview(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; + this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java b/src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java new file mode 100644 index 00000000..4e1044df --- /dev/null +++ b/src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java @@ -0,0 +1,64 @@ +package com.otakumap.domain.event_like.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QEventLike is a Querydsl query type for EventLike + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QEventLike extends EntityPathBase { + + private static final long serialVersionUID = -1375749575L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QEventLike eventLike = new QEventLike("eventLike"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final com.otakumap.domain.event.entity.QEvent event; + + public final NumberPath id = createNumber("id", Long.class); + + public final BooleanPath isFavorite = createBoolean("isFavorite"); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final com.otakumap.domain.user.entity.QUser user; + + public QEventLike(String variable) { + this(EventLike.class, forVariable(variable), INITS); + } + + public QEventLike(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QEventLike(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QEventLike(PathMetadata metadata, PathInits inits) { + this(EventLike.class, metadata, inits); + } + + public QEventLike(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; + this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java b/src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java new file mode 100644 index 00000000..83a89e49 --- /dev/null +++ b/src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java @@ -0,0 +1,73 @@ +package com.otakumap.domain.event_review.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QEventReview is a Querydsl query type for EventReview + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QEventReview extends EntityPathBase { + + private static final long serialVersionUID = -1760095975L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QEventReview eventReview = new QEventReview("eventReview"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + public final StringPath content = createString("content"); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final com.otakumap.domain.event.entity.QEvent event; + + public final NumberPath id = createNumber("id", Long.class); + + public final com.otakumap.domain.image.entity.QImage image; + + public final NumberPath rating = createNumber("rating", Float.class); + + public final StringPath title = createString("title"); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final com.otakumap.domain.user.entity.QUser user; + + public final NumberPath view = createNumber("view", Integer.class); + + public QEventReview(String variable) { + this(EventReview.class, forVariable(variable), INITS); + } + + public QEventReview(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QEventReview(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QEventReview(PathMetadata metadata, PathInits inits) { + this(EventReview.class, metadata, inits); + } + + public QEventReview(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; + this.image = inits.isInitialized("image") ? new com.otakumap.domain.image.entity.QImage(forProperty("image")) : null; + this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/image/entity/QImage.java b/src/main/generated/com/otakumap/domain/image/entity/QImage.java new file mode 100644 index 00000000..24d94565 --- /dev/null +++ b/src/main/generated/com/otakumap/domain/image/entity/QImage.java @@ -0,0 +1,51 @@ +package com.otakumap.domain.image.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QImage is a Querydsl query type for Image + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QImage extends EntityPathBase { + + private static final long serialVersionUID = 647406658L; + + public static final QImage image = new QImage("image"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final StringPath fileName = createString("fileName"); + + public final StringPath fileUrl = createString("fileUrl"); + + public final NumberPath id = createNumber("id", Long.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final StringPath uuid = createString("uuid"); + + public QImage(String variable) { + super(Image.class, forVariable(variable)); + } + + public QImage(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QImage(PathMetadata metadata) { + super(Image.class, metadata); + } + +} + diff --git a/src/main/generated/com/otakumap/domain/place/entity/QPlace.java b/src/main/generated/com/otakumap/domain/place/entity/QPlace.java new file mode 100644 index 00000000..75d275bd --- /dev/null +++ b/src/main/generated/com/otakumap/domain/place/entity/QPlace.java @@ -0,0 +1,52 @@ +package com.otakumap.domain.place.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QPlace is a Querydsl query type for Place + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPlace extends EntityPathBase { + + private static final long serialVersionUID = -824280126L; + + public static final QPlace place = new QPlace("place"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final StringPath detail = createString("detail"); + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final ListPath reviews = this.createList("reviews", com.otakumap.domain.place_short_review.entity.PlaceShortReview.class, com.otakumap.domain.place_short_review.entity.QPlaceShortReview.class, PathInits.DIRECT2); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public QPlace(String variable) { + super(Place.class, forVariable(variable)); + } + + public QPlace(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPlace(PathMetadata metadata) { + super(Place.class, metadata); + } + +} + diff --git a/src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java b/src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java new file mode 100644 index 00000000..53174e3b --- /dev/null +++ b/src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java @@ -0,0 +1,64 @@ +package com.otakumap.domain.place_like.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QPlaceLike is a Querydsl query type for PlaceLike + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPlaceLike extends EntityPathBase { + + private static final long serialVersionUID = 1027033299L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QPlaceLike placeLike = new QPlaceLike("placeLike"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final BooleanPath isFavorite = createBoolean("isFavorite"); + + public final com.otakumap.domain.place.entity.QPlace place; + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final com.otakumap.domain.user.entity.QUser user; + + public QPlaceLike(String variable) { + this(PlaceLike.class, forVariable(variable), INITS); + } + + public QPlaceLike(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QPlaceLike(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QPlaceLike(PathMetadata metadata, PathInits inits) { + this(PlaceLike.class, metadata, inits); + } + + public QPlaceLike(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.place = inits.isInitialized("place") ? new com.otakumap.domain.place.entity.QPlace(forProperty("place")) : null; + this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java b/src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java new file mode 100644 index 00000000..61ad9bff --- /dev/null +++ b/src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java @@ -0,0 +1,68 @@ +package com.otakumap.domain.place_review.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QPlaceReview is a Querydsl query type for PlaceReview + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPlaceReview extends EntityPathBase { + + private static final long serialVersionUID = 370620915L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QPlaceReview placeReview = new QPlaceReview("placeReview"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + public final StringPath content = createString("content"); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath id = createNumber("id", Long.class); + + public final com.otakumap.domain.place.entity.QPlace place; + + public final StringPath title = createString("title"); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final com.otakumap.domain.user.entity.QUser user; + + public final NumberPath view = createNumber("view", Long.class); + + public QPlaceReview(String variable) { + this(PlaceReview.class, forVariable(variable), INITS); + } + + public QPlaceReview(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QPlaceReview(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QPlaceReview(PathMetadata metadata, PathInits inits) { + this(PlaceReview.class, metadata, inits); + } + + public QPlaceReview(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.place = inits.isInitialized("place") ? new com.otakumap.domain.place.entity.QPlace(forProperty("place")) : null; + this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java b/src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java new file mode 100644 index 00000000..7f507086 --- /dev/null +++ b/src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java @@ -0,0 +1,70 @@ +package com.otakumap.domain.place_short_review.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QPlaceShortReview is a Querydsl query type for PlaceShortReview + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QPlaceShortReview extends EntityPathBase { + + private static final long serialVersionUID = 1611935772L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QPlaceShortReview placeShortReview = new QPlaceShortReview("placeShortReview"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + public final StringPath content = createString("content"); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath dislikes = createNumber("dislikes", Long.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath likes = createNumber("likes", Long.class); + + public final com.otakumap.domain.place.entity.QPlace place; + + public final NumberPath rating = createNumber("rating", Float.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final com.otakumap.domain.user.entity.QUser user; + + public QPlaceShortReview(String variable) { + this(PlaceShortReview.class, forVariable(variable), INITS); + } + + public QPlaceShortReview(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QPlaceShortReview(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QPlaceShortReview(PathMetadata metadata, PathInits inits) { + this(PlaceShortReview.class, metadata, inits); + } + + public QPlaceShortReview(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.place = inits.isInitialized("place") ? new com.otakumap.domain.place.entity.QPlace(forProperty("place")) : null; + this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/domain/user/entity/QUser.java b/src/main/generated/com/otakumap/domain/user/entity/QUser.java new file mode 100644 index 00000000..2f67b436 --- /dev/null +++ b/src/main/generated/com/otakumap/domain/user/entity/QUser.java @@ -0,0 +1,77 @@ +package com.otakumap.domain.user.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QUser is a Querydsl query type for User + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUser extends EntityPathBase { + + private static final long serialVersionUID = -1203162030L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QUser user = new QUser("user"); + + public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); + + //inherited + public final DateTimePath createdAt = _super.createdAt; + + public final NumberPath donation = createNumber("donation", Integer.class); + + public final StringPath email = createString("email"); + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath name = createString("name"); + + public final StringPath nickname = createString("nickname"); + + public final StringPath password = createString("password"); + + public final com.otakumap.domain.image.entity.QImage profileImage; + + public final EnumPath role = createEnum("role", com.otakumap.domain.user.entity.enums.Role.class); + + public final EnumPath socialType = createEnum("socialType", com.otakumap.domain.user.entity.enums.SocialType.class); + + public final EnumPath status = createEnum("status", com.otakumap.domain.user.entity.enums.UserStatus.class); + + //inherited + public final DateTimePath updatedAt = _super.updatedAt; + + public final StringPath userId = createString("userId"); + + public QUser(String variable) { + this(User.class, forVariable(variable), INITS); + } + + public QUser(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QUser(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QUser(PathMetadata metadata, PathInits inits) { + this(User.class, metadata, inits); + } + + public QUser(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.profileImage = inits.isInitialized("profileImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("profileImage")) : null; + } + +} + diff --git a/src/main/generated/com/otakumap/global/common/QBaseEntity.java b/src/main/generated/com/otakumap/global/common/QBaseEntity.java new file mode 100644 index 00000000..0c3b9988 --- /dev/null +++ b/src/main/generated/com/otakumap/global/common/QBaseEntity.java @@ -0,0 +1,39 @@ +package com.otakumap.global.common; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QBaseEntity is a Querydsl query type for BaseEntity + */ +@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") +public class QBaseEntity extends EntityPathBase { + + private static final long serialVersionUID = -1132910537L; + + public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); + + public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.LocalDateTime.class); + + public QBaseEntity(String variable) { + super(BaseEntity.class, forVariable(variable)); + } + + public QBaseEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QBaseEntity(PathMetadata metadata) { + super(BaseEntity.class, metadata); + } + +} + diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewsService.java b/src/main/java/com/otakumap/domain/reviews/service/reviewsService.java new file mode 100644 index 00000000..121a01ab --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/reviewsService.java @@ -0,0 +1,4 @@ +package com.otakumap.domain.reviews.service; + +public interface reviewsService { +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java new file mode 100644 index 00000000..8ec15ed3 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java @@ -0,0 +1,4 @@ +package com.otakumap.domain.reviews.service; + +public class reviewsServiceImpl { +} diff --git a/src/main/java/com/otakumap/global/config/QueryDSLConfig.java b/src/main/java/com/otakumap/global/config/QueryDSLConfig.java new file mode 100644 index 00000000..fcc057d2 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/QueryDSLConfig.java @@ -0,0 +1,18 @@ +package com.otakumap.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class QueryDSLConfig { + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory(){ + return new JPAQueryFactory(entityManager); + } +} From 58f6c340d4235a7938316a202ab3029e8b7569c3 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 18 Jan 2025 04:45:59 +0900 Subject: [PATCH 063/516] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20Service,=20Repository=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ReviewsRepositoryCustom.java | 7 +++ .../repository/ReviewsRepositoryImpl.java | 54 +++++++++++++++++++ .../reviews/service/reviewQueryService.java | 7 +++ .../service/reviewQueryServiceImpl.java | 22 ++++++++ .../reviews/service/reviewsService.java | 4 -- .../reviews/service/reviewsServiceImpl.java | 4 -- .../apiPayload/code/status/ErrorStatus.java | 5 +- .../exception/handler/SearchHandler.java | 10 ++++ 8 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java create mode 100644 src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/reviewQueryService.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/reviewQueryServiceImpl.java delete mode 100644 src/main/java/com/otakumap/domain/reviews/service/reviewsService.java delete mode 100644 src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/SearchHandler.java diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java new file mode 100644 index 00000000..9812d53e --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.reviews.repository; + +import java.util.List; + +public interface ReviewsRepositoryCustom { + List getReviewsByKeyword(String keyword); +} diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java new file mode 100644 index 00000000..c0c05429 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java @@ -0,0 +1,54 @@ +package com.otakumap.domain.reviews.repository; + +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.entity.QEventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.entity.QPlaceReview; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.SearchHandler; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class ReviewsRepositoryImpl implements ReviewsRepositoryCustom{ + + private final JPAQueryFactory queryFactory; + + @Override + public List getReviewsByKeyword(String keyword) { + QEventReview eventReview = QEventReview.eventReview; + QPlaceReview placeReview = QPlaceReview.placeReview; + + BooleanBuilder eventCondition = new BooleanBuilder(); + eventCondition.and(eventReview.title.containsIgnoreCase(keyword) + .or(eventReview.content.containsIgnoreCase(keyword))); + + BooleanBuilder placeCondition = new BooleanBuilder(); + placeCondition.and(placeReview.title.containsIgnoreCase(keyword) + .or(placeReview.content.containsIgnoreCase(keyword))); + + List eventReviews = queryFactory.selectFrom(eventReview) + .where(eventCondition) + .fetch(); + + List placeReviews = queryFactory.selectFrom(placeReview) + .where(placeCondition) + .fetch(); + + List combinedResults = new ArrayList<>(); + combinedResults.addAll(eventReviews); + combinedResults.addAll(placeReviews); + + if (combinedResults.isEmpty()) { + throw new SearchHandler(ErrorStatus.REVIEW_SEARCH_NOT_FOUND); + } + + return combinedResults; + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/reviewQueryService.java new file mode 100644 index 00000000..1e9fa01a --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/reviewQueryService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.reviews.service; + +import java.util.List; + +public interface reviewQueryService { + List searchReviewsByKeyword(String keyword); +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/reviewQueryServiceImpl.java new file mode 100644 index 00000000..6448a423 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/reviewQueryServiceImpl.java @@ -0,0 +1,22 @@ +package com.otakumap.domain.reviews.service; + +import com.otakumap.domain.reviews.repository.ReviewsRepositoryCustom; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class reviewQueryServiceImpl implements reviewQueryService{ + + private final ReviewsRepositoryCustom reviewsRepositoryCustom; + + @Override + public List searchReviewsByKeyword(String keyword) { + + return reviewsRepositoryCustom.getReviewsByKeyword(keyword); + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewsService.java b/src/main/java/com/otakumap/domain/reviews/service/reviewsService.java deleted file mode 100644 index 121a01ab..00000000 --- a/src/main/java/com/otakumap/domain/reviews/service/reviewsService.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.otakumap.domain.reviews.service; - -public interface reviewsService { -} diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java deleted file mode 100644 index 8ec15ed3..00000000 --- a/src/main/java/com/otakumap/domain/reviews/service/reviewsServiceImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.otakumap.domain.reviews.service; - -public class reviewsServiceImpl { -} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index d7f9f59f..3d31f43c 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -39,7 +39,10 @@ public enum ErrorStatus implements BaseErrorCode { EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."), // 명소 좋아요 관련 에러 - PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."); + PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."), + + // 후기 검색 관련 에러 + REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/SearchHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/SearchHandler.java new file mode 100644 index 00000000..4d7d6b3c --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/SearchHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class SearchHandler extends GeneralException { + public SearchHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 17ef402ce846af47568912de9f56ae6956677a00 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 18 Jan 2025 06:17:48 +0900 Subject: [PATCH 064/516] =?UTF-8?q?Fix:=20QueryDSL=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?build.gradle=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 24 +---- .../otakumap/domain/event/entity/QEvent.java | 88 ------------------- .../eventLocation/entity/QEventLocation.java | 65 -------------- .../entity/QEventShortReview.java | 70 --------------- .../domain/event_like/entity/QEventLike.java | 64 -------------- .../event_review/entity/QEventReview.java | 73 --------------- .../otakumap/domain/image/entity/QImage.java | 51 ----------- .../otakumap/domain/place/entity/QPlace.java | 52 ----------- .../domain/place_like/entity/QPlaceLike.java | 64 -------------- .../place_review/entity/QPlaceReview.java | 68 -------------- .../entity/QPlaceShortReview.java | 70 --------------- .../otakumap/domain/user/entity/QUser.java | 77 ---------------- .../otakumap/global/common/QBaseEntity.java | 39 -------- ...ryService.java => ReviewQueryService.java} | 0 ...eImpl.java => ReviewQueryServiceImpl.java} | 0 15 files changed, 3 insertions(+), 802 deletions(-) delete mode 100644 src/main/generated/com/otakumap/domain/event/entity/QEvent.java delete mode 100644 src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java delete mode 100644 src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java delete mode 100644 src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java delete mode 100644 src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java delete mode 100644 src/main/generated/com/otakumap/domain/image/entity/QImage.java delete mode 100644 src/main/generated/com/otakumap/domain/place/entity/QPlace.java delete mode 100644 src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java delete mode 100644 src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java delete mode 100644 src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java delete mode 100644 src/main/generated/com/otakumap/domain/user/entity/QUser.java delete mode 100644 src/main/generated/com/otakumap/global/common/QBaseEntity.java rename src/main/java/com/otakumap/domain/reviews/service/{reviewQueryService.java => ReviewQueryService.java} (100%) rename src/main/java/com/otakumap/domain/reviews/service/{reviewQueryServiceImpl.java => ReviewQueryServiceImpl.java} (100%) diff --git a/build.gradle b/build.gradle index 3724ffe8..5ffb7f11 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,6 @@ plugins { id 'java' id 'org.springframework.boot' version '3.4.1' id 'io.spring.dependency-management' version '1.1.7' - id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" } group = 'com' @@ -53,7 +52,7 @@ dependencies { // queryDSL implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" } @@ -63,23 +62,6 @@ tasks.named('test') { } // Querydsl 설정부 -def generated = 'src/main/generated' - -querydsl { - jpa = true - querydslSourcesDir = generated -} -sourceSets { - main.java.srcDir generated -} - -compileQuerydsl{ - options.annotationProcessorPath = configurations.querydsl -} - -configurations { - compileOnly { - extendsFrom annotationProcessor - } - querydsl.extendsFrom compileClasspath +clean { + delete file('src/main/generated') } diff --git a/src/main/generated/com/otakumap/domain/event/entity/QEvent.java b/src/main/generated/com/otakumap/domain/event/entity/QEvent.java deleted file mode 100644 index 3a7ba9c0..00000000 --- a/src/main/generated/com/otakumap/domain/event/entity/QEvent.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.otakumap.domain.event.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QEvent is a Querydsl query type for Event - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QEvent extends EntityPathBase { - - private static final long serialVersionUID = 559538850L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QEvent event = new QEvent("event"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - public final StringPath animationName = createString("animationName"); - - public final com.otakumap.domain.image.entity.QImage backgroudImage; - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final DatePath endDate = createDate("endDate", java.time.LocalDate.class); - - public final ListPath eventLikeList = this.createList("eventLikeList", com.otakumap.domain.event_like.entity.EventLike.class, com.otakumap.domain.event_like.entity.QEventLike.class, PathInits.DIRECT2); - - public final com.otakumap.domain.eventLocation.entity.QEventLocation eventLocation; - - public final EnumPath genre = createEnum("genre", com.otakumap.domain.event.entity.enums.Genre.class); - - public final com.otakumap.domain.image.entity.QImage goodsImage; - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath name = createString("name"); - - public final StringPath site = createString("site"); - - public final DatePath startDate = createDate("startDate", java.time.LocalDate.class); - - public final EnumPath status = createEnum("status", com.otakumap.domain.event.entity.enums.EventStatus.class); - - public final com.otakumap.domain.image.entity.QImage thumbnailImage; - - public final StringPath title = createString("title"); - - public final EnumPath type = createEnum("type", com.otakumap.domain.event.entity.enums.EventType.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QEvent(String variable) { - this(Event.class, forVariable(variable), INITS); - } - - public QEvent(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QEvent(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QEvent(PathMetadata metadata, PathInits inits) { - this(Event.class, metadata, inits); - } - - public QEvent(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.backgroudImage = inits.isInitialized("backgroudImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("backgroudImage")) : null; - this.eventLocation = inits.isInitialized("eventLocation") ? new com.otakumap.domain.eventLocation.entity.QEventLocation(forProperty("eventLocation"), inits.get("eventLocation")) : null; - this.goodsImage = inits.isInitialized("goodsImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("goodsImage")) : null; - this.thumbnailImage = inits.isInitialized("thumbnailImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("thumbnailImage")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java b/src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java deleted file mode 100644 index 9ee29238..00000000 --- a/src/main/generated/com/otakumap/domain/eventLocation/entity/QEventLocation.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.otakumap.domain.eventLocation.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QEventLocation is a Querydsl query type for EventLocation - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QEventLocation extends EntityPathBase { - - private static final long serialVersionUID = -609307966L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QEventLocation eventLocation = new QEventLocation("eventLocation"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final com.otakumap.domain.event.entity.QEvent event; - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath latitude = createString("latitude"); - - public final StringPath longitude = createString("longitude"); - - public final StringPath name = createString("name"); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QEventLocation(String variable) { - this(EventLocation.class, forVariable(variable), INITS); - } - - public QEventLocation(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QEventLocation(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QEventLocation(PathMetadata metadata, PathInits inits) { - this(EventLocation.class, metadata, inits); - } - - public QEventLocation(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java b/src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java deleted file mode 100644 index 710b2c6c..00000000 --- a/src/main/generated/com/otakumap/domain/eventShortReview/entity/QEventShortReview.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.otakumap.domain.eventShortReview.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QEventShortReview is a Querydsl query type for EventShortReview - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QEventShortReview extends EntityPathBase { - - private static final long serialVersionUID = -1666552144L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QEventShortReview eventShortReview = new QEventShortReview("eventShortReview"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - public final StringPath content = createString("content"); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath dislikes = createNumber("dislikes", Integer.class); - - public final com.otakumap.domain.event.entity.QEvent event; - - public final NumberPath id = createNumber("id", Long.class); - - public final NumberPath likes = createNumber("likes", Integer.class); - - public final NumberPath rating = createNumber("rating", Float.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final com.otakumap.domain.user.entity.QUser user; - - public QEventShortReview(String variable) { - this(EventShortReview.class, forVariable(variable), INITS); - } - - public QEventShortReview(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QEventShortReview(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QEventShortReview(PathMetadata metadata, PathInits inits) { - this(EventShortReview.class, metadata, inits); - } - - public QEventShortReview(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; - this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java b/src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java deleted file mode 100644 index 4e1044df..00000000 --- a/src/main/generated/com/otakumap/domain/event_like/entity/QEventLike.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.otakumap.domain.event_like.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QEventLike is a Querydsl query type for EventLike - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QEventLike extends EntityPathBase { - - private static final long serialVersionUID = -1375749575L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QEventLike eventLike = new QEventLike("eventLike"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final com.otakumap.domain.event.entity.QEvent event; - - public final NumberPath id = createNumber("id", Long.class); - - public final BooleanPath isFavorite = createBoolean("isFavorite"); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final com.otakumap.domain.user.entity.QUser user; - - public QEventLike(String variable) { - this(EventLike.class, forVariable(variable), INITS); - } - - public QEventLike(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QEventLike(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QEventLike(PathMetadata metadata, PathInits inits) { - this(EventLike.class, metadata, inits); - } - - public QEventLike(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; - this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java b/src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java deleted file mode 100644 index 83a89e49..00000000 --- a/src/main/generated/com/otakumap/domain/event_review/entity/QEventReview.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.otakumap.domain.event_review.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QEventReview is a Querydsl query type for EventReview - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QEventReview extends EntityPathBase { - - private static final long serialVersionUID = -1760095975L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QEventReview eventReview = new QEventReview("eventReview"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - public final StringPath content = createString("content"); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final com.otakumap.domain.event.entity.QEvent event; - - public final NumberPath id = createNumber("id", Long.class); - - public final com.otakumap.domain.image.entity.QImage image; - - public final NumberPath rating = createNumber("rating", Float.class); - - public final StringPath title = createString("title"); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final com.otakumap.domain.user.entity.QUser user; - - public final NumberPath view = createNumber("view", Integer.class); - - public QEventReview(String variable) { - this(EventReview.class, forVariable(variable), INITS); - } - - public QEventReview(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QEventReview(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QEventReview(PathMetadata metadata, PathInits inits) { - this(EventReview.class, metadata, inits); - } - - public QEventReview(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.event = inits.isInitialized("event") ? new com.otakumap.domain.event.entity.QEvent(forProperty("event"), inits.get("event")) : null; - this.image = inits.isInitialized("image") ? new com.otakumap.domain.image.entity.QImage(forProperty("image")) : null; - this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/image/entity/QImage.java b/src/main/generated/com/otakumap/domain/image/entity/QImage.java deleted file mode 100644 index 24d94565..00000000 --- a/src/main/generated/com/otakumap/domain/image/entity/QImage.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.otakumap.domain.image.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QImage is a Querydsl query type for Image - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QImage extends EntityPathBase { - - private static final long serialVersionUID = 647406658L; - - public static final QImage image = new QImage("image"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final StringPath fileName = createString("fileName"); - - public final StringPath fileUrl = createString("fileUrl"); - - public final NumberPath id = createNumber("id", Long.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final StringPath uuid = createString("uuid"); - - public QImage(String variable) { - super(Image.class, forVariable(variable)); - } - - public QImage(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QImage(PathMetadata metadata) { - super(Image.class, metadata); - } - -} - diff --git a/src/main/generated/com/otakumap/domain/place/entity/QPlace.java b/src/main/generated/com/otakumap/domain/place/entity/QPlace.java deleted file mode 100644 index 75d275bd..00000000 --- a/src/main/generated/com/otakumap/domain/place/entity/QPlace.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.otakumap.domain.place.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QPlace is a Querydsl query type for Place - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QPlace extends EntityPathBase { - - private static final long serialVersionUID = -824280126L; - - public static final QPlace place = new QPlace("place"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final StringPath detail = createString("detail"); - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath name = createString("name"); - - public final ListPath reviews = this.createList("reviews", com.otakumap.domain.place_short_review.entity.PlaceShortReview.class, com.otakumap.domain.place_short_review.entity.QPlaceShortReview.class, PathInits.DIRECT2); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public QPlace(String variable) { - super(Place.class, forVariable(variable)); - } - - public QPlace(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QPlace(PathMetadata metadata) { - super(Place.class, metadata); - } - -} - diff --git a/src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java b/src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java deleted file mode 100644 index 53174e3b..00000000 --- a/src/main/generated/com/otakumap/domain/place_like/entity/QPlaceLike.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.otakumap.domain.place_like.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QPlaceLike is a Querydsl query type for PlaceLike - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QPlaceLike extends EntityPathBase { - - private static final long serialVersionUID = 1027033299L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QPlaceLike placeLike = new QPlaceLike("placeLike"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final BooleanPath isFavorite = createBoolean("isFavorite"); - - public final com.otakumap.domain.place.entity.QPlace place; - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final com.otakumap.domain.user.entity.QUser user; - - public QPlaceLike(String variable) { - this(PlaceLike.class, forVariable(variable), INITS); - } - - public QPlaceLike(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QPlaceLike(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QPlaceLike(PathMetadata metadata, PathInits inits) { - this(PlaceLike.class, metadata, inits); - } - - public QPlaceLike(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.place = inits.isInitialized("place") ? new com.otakumap.domain.place.entity.QPlace(forProperty("place")) : null; - this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java b/src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java deleted file mode 100644 index 61ad9bff..00000000 --- a/src/main/generated/com/otakumap/domain/place_review/entity/QPlaceReview.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.otakumap.domain.place_review.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QPlaceReview is a Querydsl query type for PlaceReview - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QPlaceReview extends EntityPathBase { - - private static final long serialVersionUID = 370620915L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QPlaceReview placeReview = new QPlaceReview("placeReview"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - public final StringPath content = createString("content"); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath id = createNumber("id", Long.class); - - public final com.otakumap.domain.place.entity.QPlace place; - - public final StringPath title = createString("title"); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final com.otakumap.domain.user.entity.QUser user; - - public final NumberPath view = createNumber("view", Long.class); - - public QPlaceReview(String variable) { - this(PlaceReview.class, forVariable(variable), INITS); - } - - public QPlaceReview(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QPlaceReview(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QPlaceReview(PathMetadata metadata, PathInits inits) { - this(PlaceReview.class, metadata, inits); - } - - public QPlaceReview(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.place = inits.isInitialized("place") ? new com.otakumap.domain.place.entity.QPlace(forProperty("place")) : null; - this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java b/src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java deleted file mode 100644 index 7f507086..00000000 --- a/src/main/generated/com/otakumap/domain/place_short_review/entity/QPlaceShortReview.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.otakumap.domain.place_short_review.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QPlaceShortReview is a Querydsl query type for PlaceShortReview - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QPlaceShortReview extends EntityPathBase { - - private static final long serialVersionUID = 1611935772L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QPlaceShortReview placeShortReview = new QPlaceShortReview("placeShortReview"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - public final StringPath content = createString("content"); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath dislikes = createNumber("dislikes", Long.class); - - public final NumberPath id = createNumber("id", Long.class); - - public final NumberPath likes = createNumber("likes", Long.class); - - public final com.otakumap.domain.place.entity.QPlace place; - - public final NumberPath rating = createNumber("rating", Float.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final com.otakumap.domain.user.entity.QUser user; - - public QPlaceShortReview(String variable) { - this(PlaceShortReview.class, forVariable(variable), INITS); - } - - public QPlaceShortReview(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QPlaceShortReview(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QPlaceShortReview(PathMetadata metadata, PathInits inits) { - this(PlaceShortReview.class, metadata, inits); - } - - public QPlaceShortReview(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.place = inits.isInitialized("place") ? new com.otakumap.domain.place.entity.QPlace(forProperty("place")) : null; - this.user = inits.isInitialized("user") ? new com.otakumap.domain.user.entity.QUser(forProperty("user"), inits.get("user")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/domain/user/entity/QUser.java b/src/main/generated/com/otakumap/domain/user/entity/QUser.java deleted file mode 100644 index 2f67b436..00000000 --- a/src/main/generated/com/otakumap/domain/user/entity/QUser.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.otakumap.domain.user.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QUser is a Querydsl query type for User - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QUser extends EntityPathBase { - - private static final long serialVersionUID = -1203162030L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QUser user = new QUser("user"); - - public final com.otakumap.global.common.QBaseEntity _super = new com.otakumap.global.common.QBaseEntity(this); - - //inherited - public final DateTimePath createdAt = _super.createdAt; - - public final NumberPath donation = createNumber("donation", Integer.class); - - public final StringPath email = createString("email"); - - public final NumberPath id = createNumber("id", Long.class); - - public final StringPath name = createString("name"); - - public final StringPath nickname = createString("nickname"); - - public final StringPath password = createString("password"); - - public final com.otakumap.domain.image.entity.QImage profileImage; - - public final EnumPath role = createEnum("role", com.otakumap.domain.user.entity.enums.Role.class); - - public final EnumPath socialType = createEnum("socialType", com.otakumap.domain.user.entity.enums.SocialType.class); - - public final EnumPath status = createEnum("status", com.otakumap.domain.user.entity.enums.UserStatus.class); - - //inherited - public final DateTimePath updatedAt = _super.updatedAt; - - public final StringPath userId = createString("userId"); - - public QUser(String variable) { - this(User.class, forVariable(variable), INITS); - } - - public QUser(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QUser(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QUser(PathMetadata metadata, PathInits inits) { - this(User.class, metadata, inits); - } - - public QUser(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.profileImage = inits.isInitialized("profileImage") ? new com.otakumap.domain.image.entity.QImage(forProperty("profileImage")) : null; - } - -} - diff --git a/src/main/generated/com/otakumap/global/common/QBaseEntity.java b/src/main/generated/com/otakumap/global/common/QBaseEntity.java deleted file mode 100644 index 0c3b9988..00000000 --- a/src/main/generated/com/otakumap/global/common/QBaseEntity.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.otakumap.global.common; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QBaseEntity is a Querydsl query type for BaseEntity - */ -@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") -public class QBaseEntity extends EntityPathBase { - - private static final long serialVersionUID = -1132910537L; - - public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); - - public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); - - public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.LocalDateTime.class); - - public QBaseEntity(String variable) { - super(BaseEntity.class, forVariable(variable)); - } - - public QBaseEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QBaseEntity(PathMetadata metadata) { - super(BaseEntity.class, metadata); - } - -} - diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java similarity index 100% rename from src/main/java/com/otakumap/domain/reviews/service/reviewQueryService.java rename to src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java diff --git a/src/main/java/com/otakumap/domain/reviews/service/reviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java similarity index 100% rename from src/main/java/com/otakumap/domain/reviews/service/reviewQueryServiceImpl.java rename to src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java From b2c163759d2d384e821d76547376d1d48da285db Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 18 Jan 2025 06:19:01 +0900 Subject: [PATCH 065/516] =?UTF-8?q?Feat:=20PlaceReview=EC=97=90=20image=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place_review/entity/PlaceReview.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 2f9e0c4a..41f3a0bb 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_review.entity; +import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -29,6 +30,10 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "image_id", referencedColumnName = "id") + private Image image; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; From 9b173ea28c52e8dd052f9edde3191d42c913793d Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 19 Jan 2025 22:30:47 +0900 Subject: [PATCH 066/516] =?UTF-8?q?Feat:=20OAuth=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 546d2140..9d455869 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,11 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + + // OAuth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + implementation 'com.google.code.gson:gson' } tasks.named('test') { From f923de52170731229d5a8af575252ffdee784600 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 19 Jan 2025 22:33:36 +0900 Subject: [PATCH 067/516] =?UTF-8?q?Feat:=20OAuth=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(kakao,=20naver,=20google)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 21 +- .../domain/auth/dto/AuthRequestDTO.java | 6 + .../domain/auth/dto/GoogleUserInfo.java | 9 + .../domain/auth/dto/KakaoUserInfo.java | 20 ++ .../domain/auth/dto/NaverUserInfo.java | 15 ++ .../auth/service/SocialAuthService.java | 8 + .../auth/service/SocialAuthServiceImpl.java | 221 ++++++++++++++++++ .../domain/user/converter/UserConverter.java | 31 ++- .../apiPayload/code/status/ErrorStatus.java | 1 + .../global/config/SocialProperties.java | 23 ++ src/main/resources/application.yml | 22 +- 11 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/auth/dto/GoogleUserInfo.java create mode 100644 src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java create mode 100644 src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java create mode 100644 src/main/java/com/otakumap/domain/auth/service/SocialAuthService.java create mode 100644 src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/config/SocialProperties.java diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index ca2aa71c..1072b2ef 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -3,7 +3,7 @@ import com.otakumap.domain.auth.dto.AuthRequestDTO; import com.otakumap.domain.auth.dto.AuthResponseDTO; import com.otakumap.domain.auth.jwt.dto.JwtDTO; -import com.otakumap.domain.auth.service.AuthCommandService; +import com.otakumap.domain.auth.service.*; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +18,7 @@ @RequiredArgsConstructor public class AuthController { private final AuthCommandService authCommandService; + private final SocialAuthService socialAuthService; @Operation(summary = "회원가입", description = "회원가입 기능입니다.") @PostMapping("/signup") @@ -68,4 +69,22 @@ public ApiResponse logout(HttpServletRequest request) { authCommandService.logout(request); return ApiResponse.onSuccess("로그아웃 되었습니다."); } + + @Operation(summary = "카카오 로그인", description = "카카오 인가 코드를 입력받아 로그인을 처리합니다.") + @PostMapping("/social/kakao") + public ApiResponse kakaoLogin(@Valid @RequestBody AuthRequestDTO.SocialLoginDTO request) { + return ApiResponse.onSuccess(socialAuthService.login("kakao", request)); + } + + @Operation(summary = "구글 로그인", description = "구글 인가 코드를 입력받아 로그인을 처리합니다.") + @PostMapping("/social/google") + public ApiResponse googleLogin(@Valid @RequestBody AuthRequestDTO.SocialLoginDTO request) { + return ApiResponse.onSuccess(socialAuthService.login("google", request)); + } + + @Operation(summary = "네이버 로그인", description = "네이버 인가 코드를 입력받아 로그인을 처리합니다.") + @PostMapping("/social/naver") + public ApiResponse naverLogin(@Valid @RequestBody AuthRequestDTO.SocialLoginDTO request) { + return ApiResponse.onSuccess(socialAuthService.login("naver", request)); + } } diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index 2b0b83ba..90be4ff4 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -70,4 +70,10 @@ public static class VerifyCodeDTO { @NotNull String email; } + + @Getter + public static class SocialLoginDTO { + @NotNull + String code; + } } diff --git a/src/main/java/com/otakumap/domain/auth/dto/GoogleUserInfo.java b/src/main/java/com/otakumap/domain/auth/dto/GoogleUserInfo.java new file mode 100644 index 00000000..3bb269f8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/dto/GoogleUserInfo.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.auth.dto; + +import lombok.Getter; + +@Getter +public class GoogleUserInfo { + private String email; + private String name; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java b/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java new file mode 100644 index 00000000..a8b6e8c8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.auth.dto; + +import lombok.Getter; + +@Getter +public class KakaoUserInfo { + private Properties properties; + private KakaoAccount kakao_account; + + @Getter + public static class KakaoAccount { + private String name; + private String email; + } + + @Getter + public static class Properties { + private String nickname; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java b/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java new file mode 100644 index 00000000..745363c5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java @@ -0,0 +1,15 @@ +package com.otakumap.domain.auth.dto; + +import lombok.Getter; + +@Getter +public class NaverUserInfo { + private Response response; + + @Getter + public static class Response { + private String name; + private String nickname; + private String email; + } +} diff --git a/src/main/java/com/otakumap/domain/auth/service/SocialAuthService.java b/src/main/java/com/otakumap/domain/auth/service/SocialAuthService.java new file mode 100644 index 00000000..40b2a755 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/SocialAuthService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.auth.service; + +import com.otakumap.domain.auth.dto.AuthRequestDTO; +import com.otakumap.domain.auth.dto.AuthResponseDTO; + +public interface SocialAuthService { + AuthResponseDTO.LoginResultDTO login(String provider, AuthRequestDTO.SocialLoginDTO request); +} diff --git a/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java new file mode 100644 index 00000000..0a56ec00 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java @@ -0,0 +1,221 @@ +package com.otakumap.domain.auth.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.otakumap.domain.auth.dto.*; +import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; +import com.otakumap.domain.auth.jwt.util.JwtProvider; +import com.otakumap.domain.user.converter.UserConverter; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import com.otakumap.global.config.SocialProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.math.BigInteger; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class SocialAuthServiceImpl implements SocialAuthService { + private final SocialProperties socialProperties; + private final RestTemplate restTemplate; + private final UserRepository userRepository; + private final JwtProvider jwtProvider; + private final Gson gson; + + @Override + public AuthResponseDTO.LoginResultDTO login(String provider, AuthRequestDTO.SocialLoginDTO request) { + // yml 파일의 social 아래 값을 자바 객체로 매핑 + SocialProperties.ProviderProperties properties = getProviderProperties(provider); + + // 네이버 로그인을 위한 상태코드 생성 + String state = null; + if(provider.equals("naver")) { + state = generateState(); + } + + // 인가 코드를 이용하여 AccessToken 가져옴 + String accessToken = getAccessToken( + URLDecoder.decode(request.getCode(), StandardCharsets.UTF_8), + properties.getClientId(), + properties.getClientSecret(), + properties.getRedirectUri(), + properties.getTokenUri(), + state // 네이버 + ); + + // AccessToken을 사용하여 유저 정보를 가져옴 + Object userInfo = getUserInfo( + accessToken, + properties.getUserInfoUri(), + getMethod(provider), + getUserInfoClass(provider) + ); + + return socialLogin(provider, userInfo); + } + + // 인가 코드를 이용하여 AccessToken 가져옴 + private String getAccessToken(String code, String clientId, String clientSecret, String redirectUri, String tokenUri, String state) { + // HTTP Header 생성 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + // HTTP Body 생성 + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("grant_type", "authorization_code"); + body.add("client_id", clientId); + body.add("client_secret", clientSecret); + body.add("redirect_uri", redirectUri); + body.add("code", code); + // 네이버의 경우 state 값이 필수 + if (state != null) { + body.add("state", state); + } + + // HTTP 요청 보내기 + HttpEntity> request = new HttpEntity<>(body, headers); + try { + ResponseEntity response = restTemplate.postForEntity(tokenUri, request, String.class); + if (response.getStatusCode() == HttpStatus.OK) { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(response.getBody()); + // JSON 응답에서 access_token을 추출 + System.out.println(jsonNode); + return jsonNode.get("access_token").asText(); + } + } catch (Exception e) { + throw new AuthHandler(ErrorStatus._INTERNAL_SERVER_ERROR); + } + throw new AuthHandler(ErrorStatus._INTERNAL_SERVER_ERROR); + } + + // AccessToken을 사용하여 유저 정보를 가져옴 + private T getUserInfo(String accessToken, String userInfoUri, HttpMethod httpMethod, Class userInfoClass) { + // HTTP Header 생성 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("Authorization", "Bearer " + accessToken); + + // HTTP 요청 보내기 + HttpEntity> request = new HttpEntity<>(headers); + try { + ResponseEntity response = restTemplate.exchange(userInfoUri, httpMethod, request, String.class); + if (response.getStatusCode() == HttpStatus.OK) { + // JSON 응답을 userInfoClass로 변환 + System.out.println(response.getBody()); + return gson.fromJson(response.getBody(), userInfoClass); + } + } catch (Exception e) { + throw new AuthHandler(ErrorStatus._INTERNAL_SERVER_ERROR); + } + throw new AuthHandler(ErrorStatus._INTERNAL_SERVER_ERROR); + } + + // 회원가입 & 로그인 + private AuthResponseDTO.LoginResultDTO socialLogin(String provider, T userInfo){ + Optional userOptional = userRepository.findByEmail(getEmail(provider, userInfo)); + User user; + + if (userOptional.isEmpty()) { // 회원가입 + user = createUser(provider, userInfo); + userRepository.save(user); + } else { + user = userOptional.get(); + } + + // 로그인 + PrincipalDetails memberDetails = new PrincipalDetails(user); + + // 로그인 성공 시 토큰 생성 + String accessToken = jwtProvider.createAccessToken(memberDetails, user.getId()); + String refreshToken = jwtProvider.createRefreshToken(memberDetails, user.getId()); + + return UserConverter.toLoginResultDTO(user, accessToken, refreshToken); + } + + // provider에 맞는 Properties를 반환 + private SocialProperties.ProviderProperties getProviderProperties(String provider) { + switch (provider.toLowerCase()) { + case "naver": + return socialProperties.getNaver(); + case "kakao": + return socialProperties.getKakao(); + case "google": + return socialProperties.getGoogle(); + default: + throw new AuthHandler(ErrorStatus.UNSUPPORTED_PROVIDER); + } + } + + // provider에 맞는 클래스를 반환 + private Class getUserInfoClass(String provider) { + switch (provider.toLowerCase()) { + case "kakao": + return KakaoUserInfo.class; + case "google": + return GoogleUserInfo.class; + case "naver": + return NaverUserInfo.class; + default: + throw new AuthHandler(ErrorStatus.UNSUPPORTED_PROVIDER); + } + } + + // provider에 맞는 HTTP method를 반환 + private HttpMethod getMethod(String provider) { + switch (provider.toLowerCase()) { + case "kakao", "naver": + return HttpMethod.POST; + case "google": + return HttpMethod.GET; + default: + throw new AuthHandler(ErrorStatus.UNSUPPORTED_PROVIDER); + } + } + + // provider에 맞는 email를 반환 + private String getEmail(String provider, T userInfo) { + switch (provider.toLowerCase()) { + case "kakao": + return ((KakaoUserInfo) userInfo).getKakao_account().getEmail(); + case "google": + return ((GoogleUserInfo) userInfo).getEmail(); + case "naver": + return ((NaverUserInfo) userInfo).getResponse().getEmail(); + default: + throw new AuthHandler(ErrorStatus.UNSUPPORTED_PROVIDER); + } + } + + // 네이버 로그인 시 상태코드가 필수값 + private String generateState() { + SecureRandom random = new SecureRandom(); + return new BigInteger(130, random).toString(32); + } + + // provider에 맞는 User 생성 + private User createUser(String provider, T userInfo) { + switch (provider.toLowerCase()) { + case "kakao": + return UserConverter.toKakaoUser((KakaoUserInfo) userInfo); + case "google": + return UserConverter.toGoogleUser((GoogleUserInfo) userInfo); + case "naver": + return UserConverter.toNaverUser((NaverUserInfo) userInfo); + default: + throw new AuthHandler(ErrorStatus.UNSUPPORTED_PROVIDER); + } + } +} diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index a5a1431f..6a21d201 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -1,9 +1,9 @@ package com.otakumap.domain.user.converter; -import com.otakumap.domain.auth.dto.AuthRequestDTO; -import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.auth.dto.*; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.entity.enums.Role; +import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; import java.time.LocalDateTime; @@ -54,4 +54,31 @@ public static AuthResponseDTO.VerifyCodeResultDTO toVerifyCodeResultDTO(boolean .isVerified(isVerified) .build(); } + + public static User toKakaoUser(KakaoUserInfo kakaoUserInfo) { + return User.builder() + .name(kakaoUserInfo.getKakao_account().getName()) + .nickname(kakaoUserInfo.getProperties().getNickname()) + .email(kakaoUserInfo.getKakao_account().getEmail()) + .socialType(SocialType.KAKAO) + .build(); + } + + public static User toGoogleUser(GoogleUserInfo googleUserInfo) { + return User.builder() + .name(googleUserInfo.getName()) + .nickname(googleUserInfo.getName()) + .email(googleUserInfo.getEmail()) + .socialType(SocialType.GOOGLE) + .build(); + } + + public static User toNaverUser(NaverUserInfo naverUserInfo) { + return User.builder() + .name(naverUserInfo.getResponse().getName()) + .nickname(naverUserInfo.getResponse().getNickname()) + .email(naverUserInfo.getResponse().getEmail()) + .socialType(SocialType.NAVER) + .build(); + } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index d7f9f59f..92fa5c0f 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -25,6 +25,7 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH4005", "유효하지 않은 토큰입니다."), TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH4006", "토큰이 만료되었습니다."), TOKEN_LOGGED_OUT(HttpStatus.UNAUTHORIZED, "AUTH4007", "이 토큰은 로그아웃되어 더 이상 유효하지 않습니다."), + UNSUPPORTED_PROVIDER(HttpStatus.BAD_REQUEST, "AUTH4008", "지원하지 않는 provider입니다."), // 멤버 관련 에러 USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), diff --git a/src/main/java/com/otakumap/global/config/SocialProperties.java b/src/main/java/com/otakumap/global/config/SocialProperties.java new file mode 100644 index 00000000..79658e22 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/SocialProperties.java @@ -0,0 +1,23 @@ +package com.otakumap.global.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "social") // yml 파일 'social' 아래 값을 바인딩 +public class SocialProperties { + private ProviderProperties kakao; // 'kakao' 관련 값 + private ProviderProperties google; // 'google' 관련 값 + private ProviderProperties naver; // 'naver' 관련 값 + + @Data + public static class ProviderProperties { + private String clientId; + private String clientSecret; + private String redirectUri; + private String tokenUri; + private String userInfoUri; + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 65bf9098..596dc4d0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -45,4 +45,24 @@ spring: secret: ${JWT_SECRET} token: access-expiration-time: 3600000 #1시간 - refresh-expiration-time: 604800000 #7일 \ No newline at end of file + refresh-expiration-time: 604800000 #7일 + +social: + kakao: + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + redirect-uri: ${KAKAO_REDIRECT_URI} + token-uri: ${KAKAO_TOKEN_URI} + user-info-uri: ${KAKAO_USERINFO_URI} + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + redirect-uri: ${GOOGLE_REDIRECT_URI} + token-uri: ${GOOGLE_TOKEN_URI} + user-info-uri: ${GOOGLE_USERINFO_URI} + naver: + client-id: ${NAVER_CLIENT_ID} + client-secret: ${NAVER_CLIENT_SECRET} + redirect-uri: ${NAVER_REDIRECT_URI} + token-uri: ${NAVER_TOKEN_URI} + user-info-uri: ${NAVER_USERINFO_URI} \ No newline at end of file From 6ca2dfbe4e8ac144c14ea299628f344f566c0d59 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 19 Jan 2025 22:34:50 +0900 Subject: [PATCH 068/516] =?UTF-8?q?Refactor:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=EC=84=9C=20userId=EC=99=80=20password?= =?UTF-8?q?=EB=A5=BC=20=EC=84=A0=ED=83=9D=EC=A0=81=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index c03d9ada..cc04c97d 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -1,5 +1,6 @@ package com.otakumap.domain.user.entity; +import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; @@ -24,10 +25,10 @@ public class User extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(length = 20, nullable = false) + @Column(length = 20) private String userId; - @Column(nullable = false) + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String password; @Column(length = 30, nullable = false) From 64a6ca7b3e58351d70f58979d351573f04c8ed39 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 19 Jan 2025 22:35:56 +0900 Subject: [PATCH 069/516] =?UTF-8?q?Feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4,=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B2=84,=20=EA=B5=AC=EA=B8=80=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20RestTemplate?= =?UTF-8?q?=20=EB=B9=88=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/WebConfig.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java index 1e20017c..173e0364 100644 --- a/src/main/java/com/otakumap/global/config/WebConfig.java +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -2,7 +2,9 @@ import com.otakumap.domain.auth.jwt.resolver.CurrentUserResolver; import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -17,4 +19,9 @@ public class WebConfig implements WebMvcConfigurer { public void addArgumentResolvers(List resolvers) { resolvers.add(authenticatedUserResolver); } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } \ No newline at end of file From 25552bd4e3f9bf34afafb96d7c23e60e4888402f Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 19 Jan 2025 23:54:16 +0900 Subject: [PATCH 070/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=EC=97=90=20=EA=B3=A0=EC=9C=A0=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=83=9D=EC=84=B1=ED=95=B4=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/dto/KakaoUserInfo.java | 6 ---- .../domain/auth/dto/NaverUserInfo.java | 1 - .../domain/user/converter/UserConverter.java | 7 ++-- .../otakumap/global/util/UuidGenerator.java | 36 +++++++++++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/otakumap/global/util/UuidGenerator.java diff --git a/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java b/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java index a8b6e8c8..1e9f5551 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java +++ b/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java @@ -4,7 +4,6 @@ @Getter public class KakaoUserInfo { - private Properties properties; private KakaoAccount kakao_account; @Getter @@ -12,9 +11,4 @@ public static class KakaoAccount { private String name; private String email; } - - @Getter - public static class Properties { - private String nickname; - } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java b/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java index 745363c5..6d6762f5 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java +++ b/src/main/java/com/otakumap/domain/auth/dto/NaverUserInfo.java @@ -9,7 +9,6 @@ public class NaverUserInfo { @Getter public static class Response { private String name; - private String nickname; private String email; } } diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 6a21d201..277b593a 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -5,6 +5,7 @@ import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; +import com.otakumap.global.util.UuidGenerator; import java.time.LocalDateTime; @@ -58,7 +59,7 @@ public static AuthResponseDTO.VerifyCodeResultDTO toVerifyCodeResultDTO(boolean public static User toKakaoUser(KakaoUserInfo kakaoUserInfo) { return User.builder() .name(kakaoUserInfo.getKakao_account().getName()) - .nickname(kakaoUserInfo.getProperties().getNickname()) + .nickname(UuidGenerator.generateUuid()) .email(kakaoUserInfo.getKakao_account().getEmail()) .socialType(SocialType.KAKAO) .build(); @@ -67,7 +68,7 @@ public static User toKakaoUser(KakaoUserInfo kakaoUserInfo) { public static User toGoogleUser(GoogleUserInfo googleUserInfo) { return User.builder() .name(googleUserInfo.getName()) - .nickname(googleUserInfo.getName()) + .nickname(UuidGenerator.generateUuid()) .email(googleUserInfo.getEmail()) .socialType(SocialType.GOOGLE) .build(); @@ -76,7 +77,7 @@ public static User toGoogleUser(GoogleUserInfo googleUserInfo) { public static User toNaverUser(NaverUserInfo naverUserInfo) { return User.builder() .name(naverUserInfo.getResponse().getName()) - .nickname(naverUserInfo.getResponse().getNickname()) + .nickname(UuidGenerator.generateUuid()) .email(naverUserInfo.getResponse().getEmail()) .socialType(SocialType.NAVER) .build(); diff --git a/src/main/java/com/otakumap/global/util/UuidGenerator.java b/src/main/java/com/otakumap/global/util/UuidGenerator.java new file mode 100644 index 00000000..1783a442 --- /dev/null +++ b/src/main/java/com/otakumap/global/util/UuidGenerator.java @@ -0,0 +1,36 @@ +package com.otakumap.global.util; + +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +public class UuidGenerator { + public static String generateUuid() { + // UUID를 문자열로 변환 + String uuid = UUID.randomUUID().toString(); + + // 문자열을 UTF-8로 인코딩하여 바이트 배열로 변환 + byte[] uuidBytes = uuid.getBytes(StandardCharsets.UTF_8); + byte[] hash; + try { + // SHA-256 해시 함수를 사용하여 바이트 배열 해싱 + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + hash = digest.digest(uuidBytes); + } catch (NoSuchAlgorithmException e) { + throw new AuthHandler(ErrorStatus._INTERNAL_SERVER_ERROR); + } + // 앞 4바이트를 16진수 문자열로 변환 + StringBuilder uniqueId = new StringBuilder(); + for (int i = 0; i < 4; i++) { + // 각 바이트를 2자리 16진수로 변환하여 결합 + uniqueId.append(String.format("%02x", hash[i])); + } + + // 최종적으로 8자리 고유값 생성 + return uniqueId.toString(); + } +} From 29174e7bf2e7687ad2d49dde6675f6b8e55d661b Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 00:34:17 +0900 Subject: [PATCH 071/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/entity/Notification.java | 33 +++++++++++++++++++ .../repository/NotificationRepository.java | 7 ++++ .../domain/user/contoller/UserController.java | 27 +++++++++++++++ .../domain/user/converter/UserConverter.java | 12 +++++++ .../domain/user/dto/UserResponseDTO.java | 21 ++++++++++++ .../com/otakumap/domain/user/entity/User.java | 5 +++ .../domain/user/service/UserQueryService.java | 1 + .../user/service/UserQueryServiceImpl.java | 5 +++ 8 files changed, 111 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/notification/entity/Notification.java create mode 100644 src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java create mode 100644 src/main/java/com/otakumap/domain/user/contoller/UserController.java create mode 100644 src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/notification/entity/Notification.java b/src/main/java/com/otakumap/domain/notification/entity/Notification.java new file mode 100644 index 00000000..3165ce1d --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/entity/Notification.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.notification.entity; + +import com.otakumap.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Notification { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ColumnDefault("true") + private Boolean community_activity; + + @ColumnDefault("true") + private Boolean event_benefits_info; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + public Notification(User user) { + this.user = user; + this.community_activity = true; + this.event_benefits_info = true; + } +} diff --git a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java new file mode 100644 index 00000000..739fada8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.notification.repository; + +import com.otakumap.domain.notification.entity.Notification; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NotificationRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java new file mode 100644 index 00000000..e907a99c --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.user.contoller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.user.converter.UserConverter; +import com.otakumap.domain.user.dto.UserResponseDTO; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.service.UserQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class UserController { + private final UserQueryService userQueryService; + + @GetMapping("/users") + @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") + public ApiResponse getUserInfo(@CurrentUser User user) { + User userInfo = userQueryService.getUserInfo(user.getId()); + return ApiResponse.onSuccess(UserConverter.toUserInfoResponseDTO(userInfo)); + } +} diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index a5a1431f..d89a1526 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -2,6 +2,7 @@ import com.otakumap.domain.auth.dto.AuthRequestDTO; import com.otakumap.domain.auth.dto.AuthResponseDTO; +import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.UserStatus; @@ -54,4 +55,15 @@ public static AuthResponseDTO.VerifyCodeResultDTO toVerifyCodeResultDTO(boolean .isVerified(isVerified) .build(); } + + public static UserResponseDTO.UserInfoResponseDTO toUserInfoResponseDTO(User user) { + return UserResponseDTO.UserInfoResponseDTO.builder() + .profileImageUrl(user.getProfileImage() == null ? null : user.getProfileImage().getFileUrl()) + .nickname(user.getNickname()) + .email(user.getEmail()) + .donation(user.getDonation()) + .community_activity(user.getNotification().getCommunity_activity()) + .event_benefits_info(user.getNotification().getEvent_benefits_info()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java new file mode 100644 index 00000000..29252a95 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class UserResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class UserInfoResponseDTO { + private String profileImageUrl; + private String nickname; + private String email; + private Integer donation; + private boolean community_activity; + private boolean event_benefits_info; + } +} diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index c03d9ada..d4155e71 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -1,6 +1,7 @@ package com.otakumap.domain.user.entity; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.notification.entity.Notification; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; @@ -62,4 +63,8 @@ public class User extends BaseEntity { public void encodePassword(String password) { this.password = password; } + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Notification notification; + } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java index 2e9c0fba..f7a37f2e 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java @@ -4,4 +4,5 @@ public interface UserQueryService { User getUserByEmail(String email); + User getUserInfo(Long userId); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index d3ac4557..d884ddeb 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -16,4 +16,9 @@ public class UserQueryServiceImpl implements UserQueryService { public User getUserByEmail(String email) { return userRepository.findByEmail(email).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); } + + @Override + public User getUserInfo(Long userId) { + return userRepository.findById(userId).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + } } From 7143af2f6365acee250a4f57e8da96097425943f Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 16:39:52 +0900 Subject: [PATCH 072/516] =?UTF-8?q?Feat:=20User,=20Notification=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/entity/Notification.java | 29 +++++++++++-------- .../domain/user/converter/UserConverter.java | 6 ++-- .../com/otakumap/domain/user/entity/User.java | 10 ++++--- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/otakumap/domain/notification/entity/Notification.java b/src/main/java/com/otakumap/domain/notification/entity/Notification.java index 3165ce1d..d386d90b 100644 --- a/src/main/java/com/otakumap/domain/notification/entity/Notification.java +++ b/src/main/java/com/otakumap/domain/notification/entity/Notification.java @@ -1,33 +1,38 @@ package com.otakumap.domain.notification.entity; import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.ColumnDefault; +import java.time.LocalDateTime; + @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class Notification { +public class Notification extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ColumnDefault("true") - private Boolean community_activity; + @Column(nullable = false) + private String type; + + @Column(nullable = false, length = 255) + private String message; + + @Column(nullable = false) + private String url; - @ColumnDefault("true") - private Boolean event_benefits_info; + @Column(nullable = false) + private Boolean isRead = false; - @OneToOne(fetch = FetchType.LAZY) + private LocalDateTime readAt; + + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; - - public Notification(User user) { - this.user = user; - this.community_activity = true; - this.event_benefits_info = true; - } } diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index d89a1526..daea1844 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -17,6 +17,8 @@ public static User toUser(AuthRequestDTO.SignupDTO request) { .userId(request.getUserId()) .email(request.getEmail()) .password(request.getPassword()) + .isCommunityActivityNotified(true) + .isEventBenefitsNotified(true) .role(Role.USER) .status(UserStatus.ACTIVE) .build(); @@ -62,8 +64,8 @@ public static UserResponseDTO.UserInfoResponseDTO toUserInfoResponseDTO(User use .nickname(user.getNickname()) .email(user.getEmail()) .donation(user.getDonation()) - .community_activity(user.getNotification().getCommunity_activity()) - .event_benefits_info(user.getNotification().getEvent_benefits_info()) + .community_activity(user.getIsCommunityActivityNotified()) + .event_benefits_info(user.getIsEventBenefitsNotified()) .build(); } } diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index d4155e71..151e5543 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -48,6 +48,12 @@ public class User extends BaseEntity { @Column(nullable = false) private Integer donation; + @Column(nullable = false) + private Boolean isCommunityActivityNotified = true; + + @Column(nullable = false) + private Boolean isEventBenefitsNotified = true; + @Enumerated(EnumType.STRING) @Column(columnDefinition = "VARCHAR(10) DEFAULT 'ACTIVE'", nullable = false) private UserStatus status; @@ -63,8 +69,4 @@ public class User extends BaseEntity { public void encodePassword(String password) { this.password = password; } - - @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - private Notification notification; - } From e1591cbec6b1839dbdbf4dd4c443efd027efa0e9 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:10:31 +0900 Subject: [PATCH 073/516] =?UTF-8?q?Feat:=20Animation,=20EventAnimation,=20?= =?UTF-8?q?PlaceAnimation=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/entity/Animation.java | 20 ++++++++++++++ .../domain/mapping/entity/EventAnimation.java | 27 +++++++++++++++++++ .../domain/mapping/entity/PlaceAnimation.java | 27 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/animation/entity/Animation.java create mode 100644 src/main/java/com/otakumap/domain/mapping/entity/EventAnimation.java create mode 100644 src/main/java/com/otakumap/domain/mapping/entity/PlaceAnimation.java diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java new file mode 100644 index 00000000..e62c9c1c --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.animation.entity; + +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Animation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) // 이벤트 일본어 원제 + private String name; +} diff --git a/src/main/java/com/otakumap/domain/mapping/entity/EventAnimation.java b/src/main/java/com/otakumap/domain/mapping/entity/EventAnimation.java new file mode 100644 index 00000000..f875c9c8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/entity/EventAnimation.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.mapping.entity; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventAnimation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id") + private Event event; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "animation_id") + private Animation animation; +} diff --git a/src/main/java/com/otakumap/domain/mapping/entity/PlaceAnimation.java b/src/main/java/com/otakumap/domain/mapping/entity/PlaceAnimation.java new file mode 100644 index 00000000..e3eac0a5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/entity/PlaceAnimation.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.mapping.entity; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceAnimation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "animation_id") + private Animation animation; +} From 6f1b8533b57a155c4240f3f251522959091d9546 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:47:15 +0900 Subject: [PATCH 074/516] =?UTF-8?q?Feat:=20Event,=20Place=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/event/entity/Event.java | 4 ++++ src/main/java/com/otakumap/domain/place/entity/Place.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index a5c9350f..b1c1f1cb 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -6,6 +6,7 @@ import com.otakumap.domain.eventLocation.entity.EventLocation; import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.mapping.entity.EventAnimation; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -74,4 +75,7 @@ public class Event extends BaseEntity { @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) private List eventLikeList = new ArrayList<>(); + + @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) + private List eventAnimationList = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 9d916a46..f6ba48c3 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.entity; +import com.otakumap.domain.mapping.entity.PlaceAnimation; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -27,4 +28,7 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List reviews = new ArrayList<>(); + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List placeAnimationList = new ArrayList<>(); } From 86133453f9e9b255e457700344f7f70bed0b1e87 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 19:19:21 +0900 Subject: [PATCH 075/516] =?UTF-8?q?Feat:=20=EA=B4=80=EB=A0=A8=20=ED=82=A4?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=EB=A1=9C=20=EC=97=AC=ED=96=89=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EA=B2=80=EC=83=89=ED=95=98=EA=B8=B0=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 2 +- .../controller/ReviewSearchController.java | 44 ++++++++++++ .../reviews/converter/ReviewConverter.java | 35 ++++++++++ .../domain/reviews/dto/ReviewResponseDTO.java | 27 ++++++++ .../repository/ReviewsRepositoryCustom.java | 5 +- .../repository/ReviewsRepositoryImpl.java | 68 ++++++++++++++++--- .../reviews/service/ReviewQueryService.java | 7 +- .../service/ReviewQueryServiceImpl.java | 10 +-- 8 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java create mode 100644 src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java create mode 100644 src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index d74ac552..2ed19347 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -28,7 +28,7 @@ public class EventReview extends BaseEntity { private String content; @Column(columnDefinition = "bigint default 0 not null") - private Integer view; + private Long view; @Column(nullable = false) private Float rating; diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java new file mode 100644 index 00000000..19810982 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -0,0 +1,44 @@ +package com.otakumap.domain.reviews.controller; + +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.service.ReviewQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +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.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class ReviewSearchController { + + private final ReviewQueryService reviewQueryService; + + @GetMapping("/api/reviews/search") + @Operation(summary = "키워드로 여행 후기 검색", description = "키워드로 여행 후기를 검색해서 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "keyword", description = "검색 키워드입니다."), + @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), + @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), + @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") + }) + public ApiResponse> getSearchedReviewList(@RequestParam String keyword, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "latest") String sort) { + + Page searchResults = reviewQueryService.searchReviewsByKeyword(keyword, page, size, sort); + + return ApiResponse.onSuccess(searchResults); + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java new file mode 100644 index 00000000..1c05f4c3 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -0,0 +1,35 @@ +package com.otakumap.domain.reviews.converter; + +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; + +public class ReviewConverter { + + public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPreviewDTO(EventReview eventReview) { + return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() + .reviewId(eventReview.getId()) + .id(eventReview.getEvent().getId()) + .title(eventReview.getTitle()) + .content(eventReview.getContent()) + .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .view(eventReview.getView()) + .createdAt(eventReview.getCreatedAt()) + .type("event") + .build(); + } + + public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview) { + return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() + .reviewId(placeReview.getId()) + .id(placeReview.getPlace().getId()) + .title(placeReview.getTitle()) + .content(placeReview.getContent()) + .reviewImage(ImageConverter.toImageDTO(placeReview.getImage())) + .view(placeReview.getView()) + .createdAt(placeReview.getCreatedAt()) + .type("place") + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java new file mode 100644 index 00000000..434e672c --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.reviews.dto; + +import com.otakumap.domain.image.dto.ImageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class ReviewResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchedReviewPreViewDTO { + Long reviewId; // 검색된 Review의 id + Long id; // Event 또는 Place의 id + String title; + String content; + String type; // "event" 또는 "place" + Long view; + LocalDateTime createdAt; + ImageResponseDTO.ImageDTO reviewImage; + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java index 9812d53e..0c11e696 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java @@ -1,7 +1,8 @@ package com.otakumap.domain.reviews.repository; -import java.util.List; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import org.springframework.data.domain.Page; public interface ReviewsRepositoryCustom { - List getReviewsByKeyword(String keyword); + Page getReviewsByKeyword(String keyword, int page, int size, String sort); } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java index c0c05429..d1a48c5e 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java @@ -1,17 +1,28 @@ package com.otakumap.domain.reviews.repository; +import com.otakumap.domain.animation.entity.QAnimation; +import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.entity.QEventReview; +import com.otakumap.domain.mapping.entity.QEventAnimation; +import com.otakumap.domain.mapping.entity.QPlaceAnimation; +import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; +import com.otakumap.domain.reviews.converter.ReviewConverter; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.SearchHandler; import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Repository; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; @Repository @@ -21,34 +32,71 @@ public class ReviewsRepositoryImpl implements ReviewsRepositoryCustom{ private final JPAQueryFactory queryFactory; @Override - public List getReviewsByKeyword(String keyword) { + public Page getReviewsByKeyword(String keyword, int page, int size, String sort) { QEventReview eventReview = QEventReview.eventReview; QPlaceReview placeReview = QPlaceReview.placeReview; + QEventAnimation eventAnimation = QEventAnimation.eventAnimation; + QPlaceAnimation placeAnimation = QPlaceAnimation.placeAnimation; + QAnimation animation = QAnimation.animation; + // 이벤트 리뷰 검색 : EventReview 제목, 내용, 또는 연관된 애니메이션 이름 BooleanBuilder eventCondition = new BooleanBuilder(); - eventCondition.and(eventReview.title.containsIgnoreCase(keyword) - .or(eventReview.content.containsIgnoreCase(keyword))); + eventCondition.or(eventReview.title.containsIgnoreCase(keyword) + .or(eventReview.content.containsIgnoreCase(keyword)) + .or(eventAnimation.animation.name.containsIgnoreCase(keyword)) + ); + // 장소 리뷰 검색 : PlaceReview 제목, 내용, 또는 연관된 애니메이션 이름 BooleanBuilder placeCondition = new BooleanBuilder(); - placeCondition.and(placeReview.title.containsIgnoreCase(keyword) - .or(placeReview.content.containsIgnoreCase(keyword))); + placeCondition.or(placeReview.title.containsIgnoreCase(keyword) + .or(placeReview.content.containsIgnoreCase(keyword)) + .or(placeAnimation.animation.name.containsIgnoreCase(keyword)) + ); List eventReviews = queryFactory.selectFrom(eventReview) + .leftJoin(eventReview.event, QEvent.event) + .leftJoin(QEvent.event.eventAnimationList, eventAnimation) + .leftJoin(eventAnimation.animation, animation) .where(eventCondition) .fetch(); List placeReviews = queryFactory.selectFrom(placeReview) + .leftJoin(placeReview.place, QPlace.place) + .leftJoin(QPlace.place.placeAnimationList, placeAnimation) + .leftJoin(placeAnimation.animation, animation) .where(placeCondition) .fetch(); - List combinedResults = new ArrayList<>(); - combinedResults.addAll(eventReviews); - combinedResults.addAll(placeReviews); + List searchedReviews = new ArrayList<>(); - if (combinedResults.isEmpty()) { + for(EventReview review : eventReviews) { + searchedReviews.add(ReviewConverter.toSearchedEventReviewPreviewDTO(review)); + } + + for(PlaceReview review : placeReviews) { + searchedReviews.add(ReviewConverter.toSearchedPlaceReviewPreviewDTO(review)); + } + + if (searchedReviews.isEmpty()) { throw new SearchHandler(ErrorStatus.REVIEW_SEARCH_NOT_FOUND); } - return combinedResults; + // 정렬 (최신순, 조회수) + if ("views".equalsIgnoreCase(sort)) { + searchedReviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getView).reversed() + .thenComparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt) // 조회수가 같으면 최신순을 기준으로 + ); + } else { + searchedReviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt).reversed()); + } + + // 페이징 + int start = page * size; + int end = Math.min(start + size, searchedReviews.size()); + if (start > end) { + return new PageImpl<>(new ArrayList<>(), PageRequest.of(page, size), searchedReviews.size()); + } + + return new PageImpl<>(searchedReviews.subList(start, end), PageRequest.of(page, size), searchedReviews.size()); } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index 1e9fa01a..e29c966d 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -1,7 +1,8 @@ package com.otakumap.domain.reviews.service; -import java.util.List; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import org.springframework.data.domain.Page; -public interface reviewQueryService { - List searchReviewsByKeyword(String keyword); +public interface ReviewQueryService { + Page searchReviewsByKeyword(String keyword, int page, int size, String sort); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 6448a423..747cb610 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -1,22 +1,22 @@ package com.otakumap.domain.reviews.service; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.repository.ReviewsRepositoryCustom; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class reviewQueryServiceImpl implements reviewQueryService{ +public class ReviewQueryServiceImpl implements ReviewQueryService { private final ReviewsRepositoryCustom reviewsRepositoryCustom; @Override - public List searchReviewsByKeyword(String keyword) { + public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { - return reviewsRepositoryCustom.getReviewsByKeyword(keyword); + return reviewsRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } } From 27e392c3e4a4778da1900133af915199955f44ee Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 19:29:09 +0900 Subject: [PATCH 076/516] =?UTF-8?q?Feat:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 16 ++++++++++--- .../domain/user/dto/UserRequestDTO.java | 10 ++++++++ .../com/otakumap/domain/user/entity/User.java | 2 ++ .../user/service/UserCommandService.java | 8 +++++++ .../user/service/UserCommandServiceImpl.java | 24 +++++++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/user/service/UserCommandService.java create mode 100644 src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index e907a99c..30f3ca9c 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -2,21 +2,22 @@ import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.user.converter.UserConverter; +import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.service.UserCommandService; import com.otakumap.domain.user.service.UserQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api") public class UserController { private final UserQueryService userQueryService; + private final UserCommandService userCommandService; @GetMapping("/users") @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") @@ -24,4 +25,13 @@ public ApiResponse getUserInfo(@CurrentUser User userInfo = userQueryService.getUserInfo(user.getId()); return ApiResponse.onSuccess(UserConverter.toUserInfoResponseDTO(userInfo)); } + + @PatchMapping("/users/nickname") + @Operation(summary = "닉네임 변경 API", description = "회원의 닉네임을 변경합니다.") + public ApiResponse updateNickname( + @CurrentUser User user, + @RequestBody UserRequestDTO.UpdateNicknameDTO request) { + userCommandService.updateNickname(user, request); + return ApiResponse.onSuccess("닉네임이 성공적으로 수정되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java new file mode 100644 index 00000000..7337ec4b --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.user.dto; + +import lombok.Getter; + +public class UserRequestDTO { + @Getter + public static class UpdateNicknameDTO { + private String nickname; + } +} diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 151e5543..de452076 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -69,4 +69,6 @@ public class User extends BaseEntity { public void encodePassword(String password) { this.password = password; } + + public void setNickname(String nickname) { this.nickname = nickname; } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java new file mode 100644 index 00000000..3abc85be --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.user.service; + +import com.otakumap.domain.user.dto.UserRequestDTO; +import com.otakumap.domain.user.entity.User; + +public interface UserCommandService { + void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request); +} diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java new file mode 100644 index 00000000..2cd408db --- /dev/null +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -0,0 +1,24 @@ +package com.otakumap.domain.user.service; + +import com.otakumap.domain.user.dto.UserRequestDTO; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserCommandServiceImpl implements UserCommandService { + private final UserRepository userRepository; + + @Override + @Transactional + public void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request) { + if (userRepository.existsByNickname(request.getNickname())) { + throw new IllegalArgumentException("이미 사용중인 닉네임입니다."); + } + user.setNickname(request.getNickname()); + userRepository.save(user); + } +} From ca0c111ebe364232793ecdcc2aa411b39c5960bf Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 19:37:16 +0900 Subject: [PATCH 077/516] =?UTF-8?q?Feat:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B2=B4=ED=81=AC=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/user/service/UserCommandServiceImpl.java | 4 +++- .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 2cd408db..80e2b493 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -3,6 +3,8 @@ import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -16,7 +18,7 @@ public class UserCommandServiceImpl implements UserCommandService { @Transactional public void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request) { if (userRepository.existsByNickname(request.getNickname())) { - throw new IllegalArgumentException("이미 사용중인 닉네임입니다."); + throw new UserHandler(ErrorStatus.NICKNAME_ALREADY_EXISTS); } user.setNickname(request.getNickname()); userRepository.save(user); diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index d7f9f59f..abb81c84 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -28,6 +28,7 @@ public enum ErrorStatus implements BaseErrorCode { // 멤버 관련 에러 USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER4001", "사용자가 없습니다."), + NICKNAME_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "USER4002", "이미 사용 중인 닉네임입니다."), // 명소 관련 에러 PLACE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4001", "존재하지 않는 명소입니다."), From f4885fc749d7896e1e9d38a2b031ebe23bcbfdc1 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 19:48:46 +0900 Subject: [PATCH 078/516] =?UTF-8?q?Feat:=20request=20dto=EC=97=90=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 7337ec4b..7ab8269d 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -1,10 +1,12 @@ package com.otakumap.domain.user.dto; +import jakarta.validation.constraints.NotNull; import lombok.Getter; public class UserRequestDTO { @Getter public static class UpdateNicknameDTO { + @NotNull private String nickname; } } From 5589fda3f8bc8279795d069b5499ea46bbc694fb Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 19:50:58 +0900 Subject: [PATCH 079/516] =?UTF-8?q?Feat:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 7ab8269d..6dfdcb7a 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -1,12 +1,14 @@ package com.otakumap.domain.user.dto; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Getter; public class UserRequestDTO { @Getter public static class UpdateNicknameDTO { @NotNull + @Size(min = 1, max = 20, message = "닉네임은 1자 이상 20자 이하로 입력해주세요.") private String nickname; } } From b9787ef4e6e96ae6e8a1e031c2df6c03ed2c7c36 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:38:41 +0900 Subject: [PATCH 080/516] =?UTF-8?q?Rename:=20mapping/entity=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20=ED=95=98=EC=9C=84=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/mapping/{entity => }/EventAnimation.java | 0 .../com/otakumap/domain/mapping/{entity => }/PlaceAnimation.java | 0 .../{ReviewsRepositoryCustom.java => ReviewRepositoryCustom.java} | 0 .../{ReviewsRepositoryImpl.java => ReviewRepositoryImpl.java} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/main/java/com/otakumap/domain/mapping/{entity => }/EventAnimation.java (100%) rename src/main/java/com/otakumap/domain/mapping/{entity => }/PlaceAnimation.java (100%) rename src/main/java/com/otakumap/domain/reviews/repository/{ReviewsRepositoryCustom.java => ReviewRepositoryCustom.java} (100%) rename src/main/java/com/otakumap/domain/reviews/repository/{ReviewsRepositoryImpl.java => ReviewRepositoryImpl.java} (100%) diff --git a/src/main/java/com/otakumap/domain/mapping/entity/EventAnimation.java b/src/main/java/com/otakumap/domain/mapping/EventAnimation.java similarity index 100% rename from src/main/java/com/otakumap/domain/mapping/entity/EventAnimation.java rename to src/main/java/com/otakumap/domain/mapping/EventAnimation.java diff --git a/src/main/java/com/otakumap/domain/mapping/entity/PlaceAnimation.java b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java similarity index 100% rename from src/main/java/com/otakumap/domain/mapping/entity/PlaceAnimation.java rename to src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java similarity index 100% rename from src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryCustom.java rename to src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java similarity index 100% rename from src/main/java/com/otakumap/domain/reviews/repository/ReviewsRepositoryImpl.java rename to src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java From e65a0300f421a2e4646b6538113846962d956eda Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:45:46 +0900 Subject: [PATCH 081/516] =?UTF-8?q?Rename:=20mapping/entity=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20=ED=95=98=EC=9C=84=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=93=A4=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/event/entity/Event.java | 2 +- .../java/com/otakumap/domain/mapping/EventAnimation.java | 2 +- .../java/com/otakumap/domain/mapping/PlaceAnimation.java | 2 +- src/main/java/com/otakumap/domain/place/entity/Place.java | 2 +- .../domain/reviews/repository/ReviewRepositoryCustom.java | 2 +- .../domain/reviews/repository/ReviewRepositoryImpl.java | 2 +- .../domain/reviews/service/ReviewQueryServiceImpl.java | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index b1c1f1cb..06af4de8 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -6,7 +6,7 @@ import com.otakumap.domain.eventLocation.entity.EventLocation; import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.mapping.entity.EventAnimation; +import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/otakumap/domain/mapping/EventAnimation.java b/src/main/java/com/otakumap/domain/mapping/EventAnimation.java index f875c9c8..7c00352e 100644 --- a/src/main/java/com/otakumap/domain/mapping/EventAnimation.java +++ b/src/main/java/com/otakumap/domain/mapping/EventAnimation.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.mapping.entity; +package com.otakumap.domain.mapping; import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event.entity.Event; diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java index e3eac0a5..6372a340 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java +++ b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.mapping.entity; +package com.otakumap.domain.mapping; import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.place.entity.Place; diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index f6ba48c3..69a67fda 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,6 +1,6 @@ package com.otakumap.domain.place.entity; -import com.otakumap.domain.mapping.entity.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java index 0c11e696..a24f3506 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java @@ -3,6 +3,6 @@ import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import org.springframework.data.domain.Page; -public interface ReviewsRepositoryCustom { +public interface ReviewRepositoryCustom { Page getReviewsByKeyword(String keyword, int page, int size, String sort); } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index d1a48c5e..da7b1f95 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -27,7 +27,7 @@ @Repository @RequiredArgsConstructor -public class ReviewsRepositoryImpl implements ReviewsRepositoryCustom{ +public class ReviewRepositoryImpl implements ReviewRepositoryCustom { private final JPAQueryFactory queryFactory; diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 747cb610..24c019c4 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -1,7 +1,7 @@ package com.otakumap.domain.reviews.service; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import com.otakumap.domain.reviews.repository.ReviewsRepositoryCustom; +import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -12,11 +12,11 @@ @Transactional(readOnly = true) public class ReviewQueryServiceImpl implements ReviewQueryService { - private final ReviewsRepositoryCustom reviewsRepositoryCustom; + private final ReviewRepositoryCustom reviewRepositoryCustom; @Override public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { - return reviewsRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); + return reviewRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } } From 35f30d35ac473a769a54a1e8488bf245dc62a620 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:51:06 +0900 Subject: [PATCH 082/516] =?UTF-8?q?Refactor:=20Controller=20GetMapping=20a?= =?UTF-8?q?pi=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/controller/ReviewSearchController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index 19810982..369d79c9 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -21,7 +21,7 @@ public class ReviewSearchController { private final ReviewQueryService reviewQueryService; - @GetMapping("/api/reviews/search") + @GetMapping("/reviews/search") @Operation(summary = "키워드로 여행 후기 검색", description = "키워드로 여행 후기를 검색해서 조회합니다.") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), From 8710a88dce7c91bce62e2b83c457aefcbb67b2d2 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:02:10 +0900 Subject: [PATCH 083/516] =?UTF-8?q?Refactor:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EB=A9=94=EC=84=9C=EB=93=9C,=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ReviewRepositoryImpl.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index da7b1f95..e68657e0 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -14,6 +14,7 @@ import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.SearchHandler; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.dsl.StringPath; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -40,18 +41,10 @@ public Page getReviewsByKeyword(Stri QAnimation animation = QAnimation.animation; // 이벤트 리뷰 검색 : EventReview 제목, 내용, 또는 연관된 애니메이션 이름 - BooleanBuilder eventCondition = new BooleanBuilder(); - eventCondition.or(eventReview.title.containsIgnoreCase(keyword) - .or(eventReview.content.containsIgnoreCase(keyword)) - .or(eventAnimation.animation.name.containsIgnoreCase(keyword)) - ); + BooleanBuilder eventCondition = createSearchCondition(eventReview.title, eventReview.content, eventAnimation.animation.name, keyword); // 장소 리뷰 검색 : PlaceReview 제목, 내용, 또는 연관된 애니메이션 이름 - BooleanBuilder placeCondition = new BooleanBuilder(); - placeCondition.or(placeReview.title.containsIgnoreCase(keyword) - .or(placeReview.content.containsIgnoreCase(keyword)) - .or(placeAnimation.animation.name.containsIgnoreCase(keyword)) - ); + BooleanBuilder placeCondition = createSearchCondition(placeReview.title, placeReview.content, placeAnimation.animation.name, keyword); List eventReviews = queryFactory.selectFrom(eventReview) .leftJoin(eventReview.event, QEvent.event) @@ -82,13 +75,7 @@ public Page getReviewsByKeyword(Stri } // 정렬 (최신순, 조회수) - if ("views".equalsIgnoreCase(sort)) { - searchedReviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getView).reversed() - .thenComparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt) // 조회수가 같으면 최신순을 기준으로 - ); - } else { - searchedReviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt).reversed()); - } + sortReviews(searchedReviews, sort); // 페이징 int start = page * size; @@ -99,4 +86,23 @@ public Page getReviewsByKeyword(Stri return new PageImpl<>(searchedReviews.subList(start, end), PageRequest.of(page, size), searchedReviews.size()); } + + private BooleanBuilder createSearchCondition(StringPath title, StringPath content, StringPath animationName, String keyword) { + BooleanBuilder condition = new BooleanBuilder(); + + condition.or(title.containsIgnoreCase(keyword)) + .or(content.containsIgnoreCase(keyword)) + .or(animationName.containsIgnoreCase(keyword)); + + return condition; + } + + private void sortReviews(List reviews, String sort) { + if ("views".equalsIgnoreCase(sort)) { + reviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getView).reversed() + .thenComparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt)); // 조회수가 같으면 최신순을 기준으로 + } else { + reviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt).reversed()); + } + } } From 28e66bf074e4eca9c9484fc04fb5f96972c30eb7 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 22:08:58 +0900 Subject: [PATCH 084/516] =?UTF-8?q?Feat:=20valid=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/user/contoller/UserController.java | 3 ++- .../java/com/otakumap/domain/user/dto/UserRequestDTO.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index 30f3ca9c..e643905f 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -9,6 +9,7 @@ import com.otakumap.domain.user.service.UserQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -30,7 +31,7 @@ public ApiResponse getUserInfo(@CurrentUser @Operation(summary = "닉네임 변경 API", description = "회원의 닉네임을 변경합니다.") public ApiResponse updateNickname( @CurrentUser User user, - @RequestBody UserRequestDTO.UpdateNicknameDTO request) { + @Valid @RequestBody UserRequestDTO.UpdateNicknameDTO request) { userCommandService.updateNickname(user, request); return ApiResponse.onSuccess("닉네임이 성공적으로 수정되었습니다."); } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 6dfdcb7a..99cecfc6 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -1,13 +1,13 @@ package com.otakumap.domain.user.dto; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Getter; public class UserRequestDTO { @Getter public static class UpdateNicknameDTO { - @NotNull + @NotBlank() @Size(min = 1, max = 20, message = "닉네임은 1자 이상 20자 이하로 입력해주세요.") private String nickname; } From 46ce6291bd4c6428cd8a556cc1348b97875bce6b Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:09:27 +0900 Subject: [PATCH 085/516] =?UTF-8?q?Refactor:=20=ED=8E=98=EC=9D=B4=EC=A7=95?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ReviewRepositoryImpl.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index e68657e0..2172bbc3 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -77,14 +77,7 @@ public Page getReviewsByKeyword(Stri // 정렬 (최신순, 조회수) sortReviews(searchedReviews, sort); - // 페이징 - int start = page * size; - int end = Math.min(start + size, searchedReviews.size()); - if (start > end) { - return new PageImpl<>(new ArrayList<>(), PageRequest.of(page, size), searchedReviews.size()); - } - - return new PageImpl<>(searchedReviews.subList(start, end), PageRequest.of(page, size), searchedReviews.size()); + return paginateReviews(searchedReviews, page, size); } private BooleanBuilder createSearchCondition(StringPath title, StringPath content, StringPath animationName, String keyword) { @@ -105,4 +98,15 @@ private void sortReviews(List review reviews.sort(Comparator.comparing(ReviewResponseDTO.SearchedReviewPreViewDTO::getCreatedAt).reversed()); } } + + private Page paginateReviews( + List reviews, int page, int size) { + int start = page * size; + int end = Math.min(start + size, reviews.size()); + if (start > end) { + return new PageImpl<>(new ArrayList<>(), PageRequest.of(page, size), reviews.size()); + } + + return new PageImpl<>(reviews.subList(start, end), PageRequest.of(page, size), reviews.size()); + } } From 1f6e80407dca90b292dca85432fcd1c9420443ed Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 22:12:16 +0900 Subject: [PATCH 086/516] Feat: merge w/ dev --- .../java/com/otakumap/domain/user/converter/UserConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 2673c829..1984a395 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -1,6 +1,7 @@ package com.otakumap.domain.user.converter; import com.otakumap.domain.auth.dto.*; +import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; From 023afffe9c5ace7f4e1098a9e29e3738e46dfe6d Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:15:37 +0900 Subject: [PATCH 087/516] =?UTF-8?q?Chore:=20Q=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20import=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/repository/ReviewRepositoryImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 2172bbc3..f1d13d97 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -4,8 +4,8 @@ import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.entity.QEventReview; -import com.otakumap.domain.mapping.entity.QEventAnimation; -import com.otakumap.domain.mapping.entity.QPlaceAnimation; +import com.otakumap.domain.mapping.QEventAnimation; +import com.otakumap.domain.mapping.QPlaceAnimation; import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; From 69806ad187447a4f6bf70a3f809d85d7c53f5321 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 23:10:56 +0900 Subject: [PATCH 088/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EB=B3=B4=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 14 ++++++++--- .../domain/user/dto/UserRequestDTO.java | 11 ++++++++ .../user/service/UserCommandService.java | 1 + .../user/service/UserCommandServiceImpl.java | 19 +++++++++++++- .../com/otakumap/global/util/EmailUtil.java | 25 +++++++++++++++++++ 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/otakumap/global/util/EmailUtil.java diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index e643905f..f0b0743b 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -15,19 +15,19 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/users") public class UserController { private final UserQueryService userQueryService; private final UserCommandService userCommandService; - @GetMapping("/users") + @GetMapping @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") public ApiResponse getUserInfo(@CurrentUser User user) { User userInfo = userQueryService.getUserInfo(user.getId()); return ApiResponse.onSuccess(UserConverter.toUserInfoResponseDTO(userInfo)); } - @PatchMapping("/users/nickname") + @PatchMapping("/nickname") @Operation(summary = "닉네임 변경 API", description = "회원의 닉네임을 변경합니다.") public ApiResponse updateNickname( @CurrentUser User user, @@ -35,4 +35,12 @@ public ApiResponse updateNickname( userCommandService.updateNickname(user, request); return ApiResponse.onSuccess("닉네임이 성공적으로 수정되었습니다."); } + + @PostMapping("/report-event") + @Operation(summary = "이벤트 제보 API", description = "이벤트를 제보합니다.") + public ApiResponse reportEvent( + @Valid @RequestBody UserRequestDTO.UserReportRequestDTO request) { + userCommandService.reportEvent(request); + return ApiResponse.onSuccess("이벤트 제보가 성공적으로 전송되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 99cecfc6..fa8dbd20 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -11,4 +11,15 @@ public static class UpdateNicknameDTO { @Size(min = 1, max = 20, message = "닉네임은 1자 이상 20자 이하로 입력해주세요.") private String nickname; } + + @Getter + public static class UserReportRequestDTO { + @NotBlank(message = "이벤트명을 입력해주세요.") + private String eventName; + + @NotBlank(message = "애니메이션명을 입력해주세요.") + private String animationName; + + private String additionalInfo; + } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java index 3abc85be..19bf5469 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -5,4 +5,5 @@ public interface UserCommandService { void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request); + void reportEvent(UserRequestDTO.UserReportRequestDTO request); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 80e2b493..29b51c43 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -5,6 +5,7 @@ import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import com.otakumap.global.util.EmailUtil; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -13,6 +14,7 @@ @RequiredArgsConstructor public class UserCommandServiceImpl implements UserCommandService { private final UserRepository userRepository; + private final EmailUtil emailUtil; @Override @Transactional @@ -23,4 +25,19 @@ public void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request) user.setNickname(request.getNickname()); userRepository.save(user); } -} + + @Override + @Transactional + public void reportEvent(UserRequestDTO.UserReportRequestDTO request) { + String subject = "[이벤트 제보] " + request.getEventName(); + String content = String.format( + "이벤트명: %s\n애니메이션명: %s\n추가사항: %s\n", + request.getEventName(), + request.getAnimationName(), + request.getAdditionalInfo() == null ? "없음" : request.getAdditionalInfo() + ); + + // 이메일 전송 + emailUtil.sendEmail("otakumap0123@gmail.com", subject, content); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/util/EmailUtil.java b/src/main/java/com/otakumap/global/util/EmailUtil.java new file mode 100644 index 00000000..4cc0f7fa --- /dev/null +++ b/src/main/java/com/otakumap/global/util/EmailUtil.java @@ -0,0 +1,25 @@ +package com.otakumap.global.util; + +import org.springframework.mail.MailException; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class EmailUtil { + private final JavaMailSender mailSender; + + public void sendEmail(String to, String subject, String content) { + try { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(to); + message.setSubject(subject); + message.setText(content); + mailSender.send(message); + } catch (MailException e) { + throw new RuntimeException("Failed to send email.", e); + } + } +} \ No newline at end of file From af2657c81325875bbe56ef6aa5e60d0c5237c69e Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 23:12:24 +0900 Subject: [PATCH 089/516] Refactor: Update base endpoint to /api/users --- .../com/otakumap/domain/user/contoller/UserController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index e907a99c..2ffe30cb 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -14,11 +14,11 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/users") public class UserController { private final UserQueryService userQueryService; - @GetMapping("/users") + @GetMapping @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") public ApiResponse getUserInfo(@CurrentUser User user) { User userInfo = userQueryService.getUserInfo(user.getId()); From 99d011c534ad0d6c24518829400d995edd0aac04 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 20 Jan 2025 23:13:00 +0900 Subject: [PATCH 090/516] Refactor: Update base endpoint to /api/users --- .../com/otakumap/domain/user/contoller/UserController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index e643905f..c34af104 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -15,19 +15,19 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/users") public class UserController { private final UserQueryService userQueryService; private final UserCommandService userCommandService; - @GetMapping("/users") + @GetMapping @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") public ApiResponse getUserInfo(@CurrentUser User user) { User userInfo = userQueryService.getUserInfo(user.getId()); return ApiResponse.onSuccess(UserConverter.toUserInfoResponseDTO(userInfo)); } - @PatchMapping("/users/nickname") + @PatchMapping("/nickname") @Operation(summary = "닉네임 변경 API", description = "회원의 닉네임을 변경합니다.") public ApiResponse updateNickname( @CurrentUser User user, From 029cf809b304e2d750b4d09ce4f0afdd53fbc66f Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:45:09 +0900 Subject: [PATCH 091/516] =?UTF-8?q?Feat:=20PlaceReviewConverter,=20PlaceRe?= =?UTF-8?q?viewResponseDTO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceReviewConverter.java | 45 +++++++++++++++++++ .../dto/PlaceReviewResponseDTO.java | 37 +++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index b92e1bb7..add46eee 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -1,5 +1,7 @@ package com.otakumap.domain.place_review.converter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; @@ -7,6 +9,8 @@ import com.otakumap.domain.user.entity.User; import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; public class PlaceReviewConverter { public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { @@ -27,4 +31,45 @@ public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateReques .view(0L) .build(); } + + // PlaceReview -> PlaceReviewDTO 변환 + public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { + return PlaceReviewResponseDTO.PlaceReviewDTO.builder() + .reviewId(placeReview.getId()) + .placeId(placeReview.getPlace().getId()) + .title(placeReview.getTitle()) + .content(placeReview.getContent()) + .view(placeReview.getView()) + .createdAt(placeReview.getCreatedAt()) + .reviewImage(ImageConverter.toImageDTO(placeReview.getImage())) + .build(); + } + + // 해당 장소의 애니메이션별 리뷰 그룹 생성 + public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGroupDTO(Animation animation, List reviews) { + + List reviewDTOs = reviews.stream() + .map(PlaceReviewConverter::toPlaceReviewDTO) + .toList(); + + return PlaceReviewResponseDTO.AnimationReviewGroupDTO.builder() + .animationId(animation.getId()) + .animationName(animation.getName()) + .reviews(reviewDTOs) + .build(); + } + + // 최상위 결과 DTO 생성 + public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, Map> reviewsByAnimation) { + + List animationGroups = reviewsByAnimation.entrySet().stream() + .map(entry -> toAnimationReviewGroupDTO(entry.getKey(), entry.getValue())) + .toList(); + + return PlaceReviewResponseDTO.PlaceAnimationReviewDTO.builder() + .placeId(place.getId()) + .placeName(place.getName()) + .animationGroups(animationGroups) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index b7e0aaab..02a61ced 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -1,11 +1,13 @@ package com.otakumap.domain.place_review.dto; +import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; public class PlaceReviewResponseDTO { @Builder @@ -18,4 +20,39 @@ public static class ReviewCreateResponseDTO { private String content; LocalDateTime createdAt; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceReviewDTO { + private Long reviewId; + private Long placeId; // 나중에 삭제 + String title; + String content; + Long view; + LocalDateTime createdAt; + ImageResponseDTO.ImageDTO reviewImage; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationReviewGroupDTO { + private Long animationId; + private String animationName; + private List reviews; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceAnimationReviewDTO { + private Long placeId; + private String placeName; + private List animationGroups; + } + } From fc754f4196140313c2966556cd4f9e8edbe21127 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 02:18:36 +0900 Subject: [PATCH 092/516] =?UTF-8?q?Feat:=20<=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C>=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=ED=95=B4=EC=84=9C=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_like/PlaceLikeControllerTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java diff --git a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java b/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java new file mode 100644 index 00000000..145c4dc3 --- /dev/null +++ b/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java @@ -0,0 +1,86 @@ +package com.otakumap.place_like; + +import com.otakumap.domain.place_like.controller.PlaceLikeController; +import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.service.PlaceLikeCommandService; +import com.otakumap.domain.place_like.service.PlaceLikeQueryService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@ExtendWith(MockitoExtension.class) +public class PlaceLikeControllerTest { + + @Mock + private PlaceLikeQueryService placeLikeQueryService; + + @Mock + private PlaceLikeCommandService placeLikeCommandService; + + @InjectMocks + private PlaceLikeController placeLikeController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(placeLikeController).build(); + } + + @Test + void testGetPlaceLikeList() throws Exception { + Long userId = 1L; + Long lastId = 0L; + int limit = 10; + + // PlaceLikePreViewDTO 객체 생성 + PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike1 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(1L, 1L, 101L, true); + PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike2 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(2L, 1L, 102L, false); + + // PlaceLikePreViewListDTO 객체 생성 + List placeLikes = Arrays.asList(placeLike1, placeLike2); + PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikeListDTO = new PlaceLikeResponseDTO.PlaceLikePreViewListDTO(placeLikes, true, 2L); + + when(placeLikeQueryService.getPlaceLikeList(userId, lastId, limit)).thenReturn(placeLikeListDTO); + + mockMvc.perform(get("/api/users/1/saved-places") + .param("lastId", "0") + .param("limit", "10") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.result.placeLikes[0].id").value(1)) + .andExpect(jsonPath("$.result.placeLikes[0].userId").value(1)) + .andExpect(jsonPath("$.result.placeLikes[0].placeId").value(101)) + .andExpect(jsonPath("$.result.placeLikes[0].isFavorite").value(true)); + + } + + @Test + void testDeletePlaceLike() throws Exception { + List placeIds = Arrays.asList(1L, 2L); + + // 삭제 동작 모의 + mockMvc.perform(delete("/api/saved-places") + .param("placeIds", "1,2") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + // "성공입니다."라는 메시지가 반환되면 이를 검증 + .andExpect(jsonPath("$.message").value("성공입니다.")); + } + +} From 89d3b8b70e6c893801c4e316cee9a2e99d6939f0 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 02:53:18 +0900 Subject: [PATCH 093/516] =?UTF-8?q?Feat:=20Place=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20=EB=B9=A0=EC=A7=84=20=EC=B9=BC=EB=9F=BC?= =?UTF-8?q?=EB=93=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20detail=20->=20desc?= =?UTF-8?q?ription=EC=9C=BC=EB=A1=9C=20=EB=AA=85=EC=84=B8=EC=84=9C?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place/entity/Place.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 69a67fda..277be961 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -2,10 +2,12 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -14,6 +16,7 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor +@Table(name = "place") public class Place extends BaseEntity { @Id @@ -23,8 +26,20 @@ public class Place extends BaseEntity { @Column(nullable = false, length = 20) private String name; + @Column(nullable = false) + private Double lat; + + @Column(nullable = false) + private Double lng; + @Column(nullable = false, length = 100) - private String detail; + private String description; + + private LocalDateTime savedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List reviews = new ArrayList<>(); From eb8a436b6e009d1b1b677c1b0fc241215be9e4ac Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 02:57:08 +0900 Subject: [PATCH 094/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/controller/PlaceController.java | 30 +++++++++++++++++++ .../domain/place/dto/PlaceResponseDTO.java | 17 +++++++++++ .../place/repository/PlaceRepository.java | 6 ++++ .../domain/place/service/PlaceService.java | 30 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/place/controller/PlaceController.java create mode 100644 src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceService.java diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java new file mode 100644 index 00000000..fc036335 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.place.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.place.dto.PlaceResponseDTO; +import com.otakumap.domain.place.service.PlaceService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/map") +@RequiredArgsConstructor +public class PlaceController { + + private final PlaceService placeService; + + @GetMapping("/saved-places") + @Operation(summary = "저장된 장소 조회 API", description = "현재 사용자가 저장한 장소 목록을 조회합니다.") + public ApiResponse> getSavedPlaces(@CurrentUser User user) { + // 인증된 사용자 정보를 기반으로 저장된 장소 조회 + List savedPlaces = placeService.getSavedPlaces(user.getId()); + return ApiResponse.onSuccess(savedPlaces); + } +} diff --git a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java new file mode 100644 index 00000000..c5bf3231 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java @@ -0,0 +1,17 @@ +package com.otakumap.domain.place.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class PlaceResponseDTO { + private Long id; + private String name; + private Double lat; + private Double lng; + private String description; + private LocalDateTime savedAt; +} diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 9807a873..ecdad675 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -3,5 +3,11 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Arrays; +import java.util.List; + public interface PlaceRepository extends JpaRepository { + + // 특정 사용자 ID로 저장된 장소 조회 + List findByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceService.java b/src/main/java/com/otakumap/domain/place/service/PlaceService.java new file mode 100644 index 00000000..6d64eafa --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceService.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.place.service; + +import com.otakumap.domain.place.dto.PlaceResponseDTO; +import com.otakumap.domain.place.repository.PlaceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PlaceService { + + private final PlaceRepository placeRepository; + + public List getSavedPlaces(Long userId) { + return placeRepository.findByUserId(userId).stream() + .map(place -> PlaceResponseDTO.builder() + .id(place.getId()) + .name(place.getName()) + .lat(place.getLat()) + .lng(place.getLng()) + .description(place.getDescription()) + .savedAt(place.getSavedAt()) + .build()) + .collect(Collectors.toList()); + } +} + From a6154b8caa624e3fab99d5cef4ef27cc018304fc Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 03:15:10 +0900 Subject: [PATCH 095/516] =?UTF-8?q?Refactor:=20DTO=20record=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/dto/PlaceResponseDTO.java | 22 +++++++++---------- .../domain/place/service/PlaceService.java | 17 +++++++------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java index c5bf3231..674810ac 100644 --- a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java @@ -1,17 +1,15 @@ package com.otakumap.domain.place.dto; -import lombok.Builder; -import lombok.Getter; - import java.time.LocalDateTime; -@Getter -@Builder -public class PlaceResponseDTO { - private Long id; - private String name; - private Double lat; - private Double lng; - private String description; - private LocalDateTime savedAt; + +public record PlaceResponseDTO( + Long id, + String name, + Double lat, + Double lng, + String description, + LocalDateTime savedAt +) { + // 필요한 경우, 추가적인 메서드를 여기에 정의할 수 있습니다. } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceService.java b/src/main/java/com/otakumap/domain/place/service/PlaceService.java index 6d64eafa..5a54c499 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceService.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceService.java @@ -16,15 +16,16 @@ public class PlaceService { public List getSavedPlaces(Long userId) { return placeRepository.findByUserId(userId).stream() - .map(place -> PlaceResponseDTO.builder() - .id(place.getId()) - .name(place.getName()) - .lat(place.getLat()) - .lng(place.getLng()) - .description(place.getDescription()) - .savedAt(place.getSavedAt()) - .build()) + .map(place -> new PlaceResponseDTO( + place.getId(), + place.getName(), + place.getLat(), + place.getLng(), + place.getDescription(), + place.getSavedAt() + )) .collect(Collectors.toList()); } + } From 7d329413379445ecb1c9cc91f65dcfb0fbc108c0 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 05:10:21 +0900 Subject: [PATCH 096/516] =?UTF-8?q?Feat:=20=ED=8A=B9=EC=A0=95=20=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=20=EC=A0=84=EC=B2=B4=20=ED=9B=84=EA=B8=B0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C(=ED=8E=98=EC=9D=B4=EC=A7=95=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=A0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceReviewController.java | 27 ++++++++++-- .../dto/PlaceReviewResponseDTO.java | 13 ++++++ .../place_review/entity/PlaceReview.java | 5 +++ .../repository/PlaceReviewRepository.java | 2 +- .../PlaceReviewRepositoryCustom.java | 12 ++++++ .../repository/PlaceReviewRepositoryImpl.java | 43 +++++++++++++++++++ .../service/PlaceReviewQueryService.java | 7 +++ .../service/PlaceReviewQueryServiceImpl.java | 37 ++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 6 ++- 9 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java create mode 100644 src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java create mode 100644 src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index 88a84d51..f5df232f 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -5,20 +5,21 @@ import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.service.PlaceReviewCommandService; +import com.otakumap.domain.place_review.service.PlaceReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -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; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api") public class PlaceReviewController { private final PlaceReviewCommandService placeReviewCommandService; + private final PlaceReviewQueryService placeReviewQueryService; @PostMapping("/review") @Operation(summary = "리뷰 작성") @@ -26,4 +27,22 @@ public ApiResponse createReview( PlaceReview placeReview = placeReviewCommandService.createReview(request); return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); } + + @GetMapping("/places/{placeId}/reviews") + @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") + @Parameters({ + @Parameter(name = "placeId", description = "특정 장소의 id 입니다."), + @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), + @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), + @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") + }) + public ApiResponse getPlaceReviewList(@PathVariable Long placeId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "latest") String sort) { + + PlaceReviewResponseDTO.PlaceAnimationReviewDTO results = placeReviewQueryService.getReviewsByPlace(placeId, page, size, sort); + + return ApiResponse.onSuccess(results); + } } diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 02a61ced..980d5748 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -55,4 +55,17 @@ public static class PlaceAnimationReviewDTO { private List animationGroups; } + +// @Builder +// @Getter +// @NoArgsConstructor +// @AllArgsConstructor +// public static class PlaceAnimationReviewListDTO { +// private Long placeId; +// private Integer currentPage; +// private Integer totalPages; +// private Integer totalElements; +// private List animationReviews; +// } + } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 41f3a0bb..99cc122d 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place_review.entity; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -41,4 +42,8 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id", nullable = false) private Place place; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_animation_id") + private PlaceAnimation placeAnimation; // 리뷰와 PlaceAnimation 연결 } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 092cc8e6..f93726e4 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -3,5 +3,5 @@ import com.otakumap.domain.place_review.entity.PlaceReview; import org.springframework.data.jpa.repository.JpaRepository; -public interface PlaceReviewRepository extends JpaRepository { +public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java new file mode 100644 index 00000000..7747b552 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.place_review.repository; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.place_review.entity.PlaceReview; + +import java.util.List; +import java.util.Map; + +public interface PlaceReviewRepositoryCustom { + + Map> findReviewsByPlaceWithAnimation(Long placeId, int page, int size, String sort); +} diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java new file mode 100644 index 00000000..b264ca99 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java @@ -0,0 +1,43 @@ +package com.otakumap.domain.place_review.repository; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.entity.QAnimation; +import com.otakumap.domain.mapping.QPlaceAnimation; +import com.otakumap.domain.place.entity.QPlace; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.entity.QPlaceReview; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class PlaceReviewRepositoryImpl implements PlaceReviewRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public Map> findReviewsByPlaceWithAnimation(Long placeId, int page, int size, String sort) { + + QPlaceReview placeReview = QPlaceReview.placeReview; + QAnimation animation = QAnimation.animation; + QPlaceAnimation placeAnimation = QPlaceAnimation.placeAnimation; + QPlace place = QPlace.place; + + + List reviews = queryFactory.selectFrom(placeReview) + .join(placeReview.place, place) // PlaceReview에서 Place로 조인 + .join(place.placeAnimationList, placeAnimation) // Place에서 PlaceAnimation으로 조인 + .join(placeAnimation.animation, animation) // PlaceAnimation에서 Animation으로 조인 + .where(placeReview.place.id.eq(placeId)) + .fetch(); + + // Animation을 기준으로 그룹화 + return reviews.stream() + .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryService.java new file mode 100644 index 00000000..727d78e7 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place_review.service; + +import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; + +public interface PlaceReviewQueryService { + PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort); +} diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java new file mode 100644 index 00000000..fd6097ec --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -0,0 +1,37 @@ +package com.otakumap.domain.place_review.service; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { + + private final PlaceRepository placeRepository; + private final PlaceReviewRepository placeReviewRepository; + + @Override + public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { + + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + Map> reviewsByAnimation = placeReviewRepository.findReviewsByPlaceWithAnimation(placeId, page, size, sort); + + return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, reviewsByAnimation); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 8182eb60..2e2cf3ba 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -43,7 +43,11 @@ public enum ErrorStatus implements BaseErrorCode { PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."), // 후기 검색 관련 에러 - REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."); + REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), + + // 애니메이션 관련 에러 + ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4001", "존재하지 않는 애니메이션입니다"), + PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4001", "존재하지 않는 애니메이션입니다"); private final HttpStatus httpStatus; private final String code; From ded2d4912b8b1977e9de6556aea008c9a92093ef Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 05:47:52 +0900 Subject: [PATCH 097/516] =?UTF-8?q?Feat:=20=EB=AA=85=EC=86=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EA=B8=B0=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceLikeController.java | 14 +++++++++++ .../service/PlaceLikeCommandService.java | 3 +++ .../service/PlaceLikeCommandServiceImpl.java | 23 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 7222b54e..34a000e5 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -1,8 +1,10 @@ package com.otakumap.domain.place_like.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.service.PlaceLikeCommandService; import com.otakumap.domain.place_like.service.PlaceLikeQueryService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ExistPlaceLike; import io.swagger.v3.oas.annotations.Operation; @@ -43,4 +45,16 @@ public ApiResponse deletePlaceLike(@RequestParam(required = false) @Exis placeLikeCommandService.deletePlaceLike(placeIds); return ApiResponse.onSuccess("저장된 장소가 성공적으로 삭제되었습니다"); } + + @Operation(summary = "장소 저장", description = "장소를 저장합니다.") + @PostMapping("/places/{placeId}/save") + @Parameters({ + @Parameter(name = "placeId", description = "장소 Id") + }) + public ApiResponse savePlaceLike(@PathVariable Long placeId, @CurrentUser User user) { + + placeLikeCommandService.savePlaceLike(user, placeId); + + return ApiResponse.onSuccess("장소가 성공적으로 저장되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java index 9f5764f8..5958a60b 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.service; +import com.otakumap.domain.user.entity.User; import org.springframework.stereotype.Service; import java.util.List; @@ -7,4 +8,6 @@ @Service public interface PlaceLikeCommandService { void deletePlaceLike(List placeIds); + + void savePlaceLike(User user, Long placeId); } diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java index d09f55f5..0c0fdef1 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -1,6 +1,13 @@ package com.otakumap.domain.place_like.service; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,6 +21,8 @@ public class PlaceLikeCommandServiceImpl implements PlaceLikeCommandService { private final PlaceLikeRepository placeLikeRepository; private final EntityManager entityManager; + private final UserRepository userRepository; + private final PlaceRepository placeRepository; @Override public void deletePlaceLike(List placeIds) { @@ -21,4 +30,18 @@ public void deletePlaceLike(List placeIds) { entityManager.flush(); entityManager.clear(); } + + @Override + public void savePlaceLike(User user, Long placeId) { + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + PlaceLike placeLike = PlaceLike.builder() + .user(user) + .place(place) + .isFavorite(Boolean.TRUE) + .build(); + + placeLikeRepository.save(placeLike); + } } \ No newline at end of file From 23afd38b380ec155891b287b1c6dd78bd1af1d29 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 07:26:41 +0900 Subject: [PATCH 098/516] =?UTF-8?q?Feat:=20ErrorHandler=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/apiPayload/code/status/ErrorStatus.java | 8 +++++++- .../apiPayload/exception/handler/RouteHandler.java | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 8182eb60..e0cac90c 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -43,7 +43,13 @@ public enum ErrorStatus implements BaseErrorCode { PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."), // 후기 검색 관련 에러 - REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."); + REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), + + // 루트 관련 에러 + ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), + + // 루트 좋아요 관련 에러 + ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java new file mode 100644 index 00000000..cfb7f2f0 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class RouteHandler extends GeneralException { + public RouteHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From eafa1d3219822a25315b244501cdb290bbc1e42b Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 07:31:03 +0900 Subject: [PATCH 099/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=EC=99=80=20Route=201:1=20=EA=B4=80=EA=B3=84?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event_review/entity/EventReview.java | 6 ++++++ .../otakumap/domain/place_review/entity/PlaceReview.java | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 2ed19347..d06d73a8 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -2,6 +2,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -44,4 +45,9 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", referencedColumnName = "id") + private Route route; + } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 41f3a0bb..8eba96c3 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -2,6 +2,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -41,4 +42,8 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id", nullable = false) private Place place; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", referencedColumnName = "id") + private Route route; } From 6727971c4a8326f1aaf6a0b8cfef8cff61fcc477 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 07:32:16 +0900 Subject: [PATCH 100/516] =?UTF-8?q?Feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20routeLikes=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 8f372279..94aa8f11 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; @@ -13,6 +13,8 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.List; + @Entity @Getter @DynamicUpdate @@ -67,6 +69,9 @@ public class User extends BaseEntity { @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; + @OneToMany(mappedBy = "user") + private List routeLikes; + public void encodePassword(String password) { this.password = password; } From 47b300790c9dab7ac3eaea0bdb0794e693b5f425 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 07:33:06 +0900 Subject: [PATCH 101/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/route/entity/Route.java | 30 +++++++++++++ .../route/repository/RouteRepository.java | 8 ++++ .../domain/route_item/entity/RouteItem.java | 30 +++++++++++++ .../domain/route_item/enums/ItemType.java | 6 +++ .../controller/RouteLikeController.java | 38 +++++++++++++++++ .../domain/route_like/entity/RouteLike.java | 42 +++++++++++++++++++ .../domain/route_like/enums/Status.java | 6 +++ .../repository/RouteLikeRepository.java | 11 +++++ .../service/RouteLikeCommandService.java | 7 ++++ .../service/RouteLikeCommandServiceImpl.java | 41 ++++++++++++++++++ 10 files changed, 219 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/route/entity/Route.java create mode 100644 src/main/java/com/otakumap/domain/route/repository/RouteRepository.java create mode 100644 src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java create mode 100644 src/main/java/com/otakumap/domain/route_item/enums/ItemType.java create mode 100644 src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java create mode 100644 src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java create mode 100644 src/main/java/com/otakumap/domain/route_like/enums/Status.java create mode 100644 src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java create mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java create mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java new file mode 100644 index 00000000..cce700fe --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.route.entity; + +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Route extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 50, nullable = false) + private String name; + + @OneToMany(mappedBy = "route") + private List routeLikes; + + @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) + private List routeItems; +} diff --git a/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java b/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java new file mode 100644 index 00000000..3714c8f0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.route.repository; + +import com.otakumap.domain.route.entity.Route; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RouteRepository extends JpaRepository { + +} diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java new file mode 100644 index 00000000..08ee6a80 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.route_item.entity; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_item.enums.ItemType; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class RouteItem extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Integer itemOrder; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) + private ItemType itemType; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "route_id") + private Route route; +} diff --git a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java new file mode 100644 index 00000000..a6e11057 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.route_item.enums; + +public enum ItemType { + PLACE, + EVENT +} diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java new file mode 100644 index 00000000..d3fb5f6a --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.route_like.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.route_like.service.RouteLikeCommandService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@Validated +public class RouteLikeController { + + private final RouteLikeCommandService routeLikeCommandService; + + @Operation(summary = "루트 저장", description = "루트를 저장합니다.") + @PostMapping("/routes/{routeId}/save") + @Parameters({ + @Parameter(name = "routeId", description = "루트 Id") + }) + public ApiResponse saveRouteLike(@PathVariable Long routeId, @CurrentUser User user) { + + routeLikeCommandService.saveRouteLike(user, routeId); + + return ApiResponse.onSuccess("루트가 성공적으로 저장되었습니다."); + } + + +} diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java new file mode 100644 index 00000000..450ea977 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -0,0 +1,42 @@ +package com.otakumap.domain.route_like.entity; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_like.enums.Status; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "route_like") +public class RouteLike extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "route_id") + private Route route; + + @Column(name = "is_favorite", nullable = false) + @ColumnDefault("false") + private Boolean isFavorite; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) + private Status status; +} diff --git a/src/main/java/com/otakumap/domain/route_like/enums/Status.java b/src/main/java/com/otakumap/domain/route_like/enums/Status.java new file mode 100644 index 00000000..4727849c --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/enums/Status.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.route_like.enums; + +public enum Status { + ACTIVE, + INACTIVE +} diff --git a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java new file mode 100644 index 00000000..b185c8df --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.route_like.repository; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RouteLikeRepository extends JpaRepository { + + boolean existsByUserAndRoute(User user, Route route); +} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java new file mode 100644 index 00000000..90bacef1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.route_like.service; + +import com.otakumap.domain.user.entity.User; + +public interface RouteLikeCommandService { + void saveRouteLike(User user, Long routeId); +} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java new file mode 100644 index 00000000..c3c4fd39 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -0,0 +1,41 @@ +package com.otakumap.domain.route_like.service; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.domain.route_like.enums.Status; +import com.otakumap.domain.route_like.repository.RouteLikeRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.RouteHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { + + private final RouteLikeRepository routeLikeRepository; + private final RouteRepository routeRepository; + private final UserRepository userRepository; + @Override + public void saveRouteLike(User user, Long routeId) { + + Route route = routeRepository.findById(routeId) + .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); + + if(routeLikeRepository.existsByUserAndRoute(user, route)) { + throw new RouteHandler(ErrorStatus.ROUTE_LIKE_ALREADY_EXISTS); + } + + RouteLike routeLike = RouteLike.builder() + .user(user) + .route(route) + .status(Status.ACTIVE) + .isFavorite(Boolean.TRUE) + .build(); + + routeLikeRepository.save(routeLike); + } +} From 0b3fced499f5ff318a1ce2ea79cdad75fcacc864 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 07:46:34 +0900 Subject: [PATCH 102/516] =?UTF-8?q?Feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20placeLikes=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 8f372279..1f0710c9 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; @@ -13,6 +13,8 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.List; + @Entity @Getter @DynamicUpdate @@ -67,6 +69,9 @@ public class User extends BaseEntity { @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; + @OneToMany(mappedBy = "user") + private List placeLikes; + public void encodePassword(String password) { this.password = password; } From 4bdf869ccdd7735a7048d85f5103fed5d014d0ee Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:47:25 +0900 Subject: [PATCH 103/516] =?UTF-8?q?Refactor:=20savePlaceLike=20URL=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/controller/PlaceLikeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 34a000e5..35966edf 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -47,7 +47,7 @@ public ApiResponse deletePlaceLike(@RequestParam(required = false) @Exis } @Operation(summary = "장소 저장", description = "장소를 저장합니다.") - @PostMapping("/places/{placeId}/save") + @PostMapping("/places/{placeId}") @Parameters({ @Parameter(name = "placeId", description = "장소 Id") }) From 004e9010a0371d7339e724b066852e444af29526 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 11:52:13 +0900 Subject: [PATCH 104/516] Refactor: Change Boolean to primitive boolean --- .../com/otakumap/domain/notification/entity/Notification.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/notification/entity/Notification.java b/src/main/java/com/otakumap/domain/notification/entity/Notification.java index d386d90b..d854e517 100644 --- a/src/main/java/com/otakumap/domain/notification/entity/Notification.java +++ b/src/main/java/com/otakumap/domain/notification/entity/Notification.java @@ -4,7 +4,6 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.ColumnDefault; import java.time.LocalDateTime; @@ -28,7 +27,7 @@ public class Notification extends BaseEntity { private String url; @Column(nullable = false) - private Boolean isRead = false; + private boolean isRead = false; private LocalDateTime readAt; From 4a75805dca02b9e781354a0c95c1141b27f7bc18 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:05:45 +0900 Subject: [PATCH 105/516] =?UTF-8?q?Refactor:=20placeLikes=EB=A5=BC=20new?= =?UTF-8?q?=20ArrayList<>()=EB=A1=9C=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/user/entity/User.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 1f0710c9..5739e979 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -13,6 +13,7 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.ArrayList; import java.util.List; @Entity @@ -69,8 +70,8 @@ public class User extends BaseEntity { @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; - @OneToMany(mappedBy = "user") - private List placeLikes; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List placeLikes = new ArrayList<>(); public void encodePassword(String password) { this.password = password; From 861ed4ecf0b87cff9480e835be2a59aa4c06e286 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:20:36 +0900 Subject: [PATCH 106/516] =?UTF-8?q?Feat:=20PlaceLikeConverter=20toPlaceRev?= =?UTF-8?q?iew=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_like/converter/PlaceLikeConverter.java | 10 ++++++++++ .../service/PlaceLikeCommandServiceImpl.java | 9 ++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 21031bc1..6c9eb2e5 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -1,7 +1,9 @@ package com.otakumap.domain.place_like.converter; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; +import com.otakumap.domain.user.entity.User; import java.util.List; @@ -23,4 +25,12 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikePreViewListD .lastId(lastId) .build(); } + + public static PlaceLike toPlaceLike(User user, Place place) { + return PlaceLike.builder() + .user(user) + .place(place) + .isFavorite(Boolean.TRUE) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java index 0c0fdef1..7c716a84 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -2,10 +2,10 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_like.converter.PlaceLikeConverter; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import jakarta.persistence.EntityManager; @@ -21,7 +21,6 @@ public class PlaceLikeCommandServiceImpl implements PlaceLikeCommandService { private final PlaceLikeRepository placeLikeRepository; private final EntityManager entityManager; - private final UserRepository userRepository; private final PlaceRepository placeRepository; @Override @@ -36,11 +35,7 @@ public void savePlaceLike(User user, Long placeId) { Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - PlaceLike placeLike = PlaceLike.builder() - .user(user) - .place(place) - .isFavorite(Boolean.TRUE) - .build(); + PlaceLike placeLike = PlaceLikeConverter.toPlaceLike(user, place); placeLikeRepository.save(placeLike); } From 824287007ffe27ffae9c5694d0346985a0fb6c54 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 12:23:57 +0900 Subject: [PATCH 107/516] =?UTF-8?q?Feat:=20=EC=95=8C=EB=A6=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 9 +++++++++ .../otakumap/domain/user/dto/UserRequestDTO.java | 16 +++++++++++++--- .../com/otakumap/domain/user/entity/User.java | 5 +++++ .../domain/user/service/UserCommandService.java | 1 + .../user/service/UserCommandServiceImpl.java | 10 ++++++++++ .../apiPayload/code/status/ErrorStatus.java | 5 ++++- 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index f0b0743b..a839b22d 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -43,4 +43,13 @@ public ApiResponse reportEvent( userCommandService.reportEvent(request); return ApiResponse.onSuccess("이벤트 제보가 성공적으로 전송되었습니다."); } + + @PatchMapping("/notification-settings") + @Operation(summary = "알림 설정 변경 API", description = "회원의 알림 설정을 변경합니다.") + public ApiResponse updateNotificationSettings( + @CurrentUser User user, + @Valid @RequestBody UserRequestDTO.NotificationSettingsRequestDTO request) { + userCommandService.updateNotificationSettings(user, request); + return ApiResponse.onSuccess("알림 설정이 성공적으로 업데이트되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index fa8dbd20..5ceae25a 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -1,13 +1,12 @@ package com.otakumap.domain.user.dto; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.*; import lombok.Getter; public class UserRequestDTO { @Getter public static class UpdateNicknameDTO { - @NotBlank() + @NotBlank @Size(min = 1, max = 20, message = "닉네임은 1자 이상 20자 이하로 입력해주세요.") private String nickname; } @@ -22,4 +21,15 @@ public static class UserReportRequestDTO { private String additionalInfo; } + + @Getter + public static class NotificationSettingsRequestDTO { + @NotNull + @Min(value = 1, message = "알림 타입은 1 또는 2로 입력해주세요.") + @Max(value = 2, message = "알림 타입은 1 또는 2로 입력해주세요.") + private Integer notificationType; + + @NotNull + private boolean isEnabled; + } } diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index dbdbcddf..f4c7ea99 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -72,4 +72,9 @@ public void encodePassword(String password) { } public void setNickname(String nickname) { this.nickname = nickname; } + + public void setNotification(Integer type, boolean isEnabled) { + if (type == 1) { this.isCommunityActivityNotified = isEnabled; } + else { this.isEventBenefitsNotified = isEnabled; } + } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java index 19bf5469..4c465558 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -6,4 +6,5 @@ public interface UserCommandService { void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request); void reportEvent(UserRequestDTO.UserReportRequestDTO request); + void updateNotificationSettings(User user, UserRequestDTO.NotificationSettingsRequestDTO request); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 29b51c43..cd0089da 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -40,4 +40,14 @@ public void reportEvent(UserRequestDTO.UserReportRequestDTO request) { // 이메일 전송 emailUtil.sendEmail("otakumap0123@gmail.com", subject, content); } + + @Override + @Transactional + public void updateNotificationSettings(User user, UserRequestDTO.NotificationSettingsRequestDTO request) { + if (request.getNotificationType() != 1 && request.getNotificationType() != 2) { + throw new UserHandler(ErrorStatus.INVALID_NOTIFICATION_TYPE); + } + user.setNotification(request.getNotificationType(), request.isEnabled()); + userRepository.save(user); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index beaaca29..90f80f29 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -44,7 +44,10 @@ public enum ErrorStatus implements BaseErrorCode { PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."), // 후기 검색 관련 에러 - REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."); + REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), + + // 알림 관련 에러 + INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."); private final HttpStatus httpStatus; private final String code; From 18b102d4de42dbacdfeb8cb66b14a8e28c600e8a Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 12:39:14 +0900 Subject: [PATCH 108/516] =?UTF-8?q?Feat:=20=EC=95=84=EC=9D=B4=EB=94=94=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0,=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 37 +++++++++++++------ .../domain/auth/dto/AuthRequestDTO.java | 11 ++++++ .../domain/auth/dto/AuthResponseDTO.java | 8 ++++ .../auth/service/AuthCommandService.java | 7 ++-- .../auth/service/AuthCommandServiceImpl.java | 30 +++++++++------ .../domain/auth/service/AuthQueryService.java | 6 +++ .../auth/service/AuthQueryServiceImpl.java | 18 +++++++++ .../domain/user/converter/UserConverter.java | 6 +++ .../user/repository/UserRepository.java | 3 +- 9 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java create mode 100644 src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index 1072b2ef..0b357ea4 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -3,11 +3,12 @@ import com.otakumap.domain.auth.dto.AuthRequestDTO; import com.otakumap.domain.auth.dto.AuthResponseDTO; import com.otakumap.domain.auth.jwt.dto.JwtDTO; +import com.otakumap.domain.auth.service.AuthCommandService; +import com.otakumap.domain.auth.service.AuthQueryService; import com.otakumap.domain.auth.service.*; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; -import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ @RequiredArgsConstructor public class AuthController { private final AuthCommandService authCommandService; + private final AuthQueryService authQueryService; private final SocialAuthService socialAuthService; @Operation(summary = "회원가입", description = "회원가입 기능입니다.") @@ -32,12 +34,6 @@ public ApiResponse login(@RequestBody @Valid Aut return ApiResponse.onSuccess(authCommandService.login(request)); } - @Operation(summary = "닉네임 중복 확인", description = "닉네임 중복 확인 기능입니다.") - @PostMapping("/check-nickname") - public ApiResponse checkNickname(@RequestBody @Valid AuthRequestDTO.CheckNicknameDTO request) { - return ApiResponse.onSuccess(UserConverter.toCheckNicknameResultDTO(authCommandService.checkNickname(request))); - } - @Operation(summary = "아이디 중복 확인", description = "아이디 중복 확인 기능입니다.") @PostMapping("/check-id") public ApiResponse checkId(@RequestBody @Valid AuthRequestDTO.CheckIdDTO request) { @@ -46,15 +42,15 @@ public ApiResponse checkId(@RequestBody @Valid @Operation(summary = "이메일 인증 메일 전송", description = "이메일 인증을 위한 메일 전송 기능입니다.") @PostMapping("/verify-email") - public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { + public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyEmailDTO request) { authCommandService.verifyEmail(request); - return ApiResponse.onSuccess("이메일 인증이 성공적으로 완료되었습니다."); + return ApiResponse.onSuccess("인증 메일이 성공적으로 전송되었습니다."); } - @Operation(summary = "이메일 코드 인증", description = "이메일 코드 인증 기능입니다.") + @Operation(summary = "이메일 코드 인증", description = "회원가입 시 이메일 코드 인증 기능입니다.") @PostMapping("/verify-code") public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyCodeDTO request) { - return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request))); + return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request, "signup"))); } @Operation(summary = "토큰 재발급", description = "accessToken이 만료 시 refreshToken을 통해 accessToken을 재발급합니다.") @@ -87,4 +83,23 @@ public ApiResponse googleLogin(@Valid @RequestBo public ApiResponse naverLogin(@Valid @RequestBody AuthRequestDTO.SocialLoginDTO request) { return ApiResponse.onSuccess(socialAuthService.login("naver", request)); } + + @Operation(summary = "아이디 찾기", description = "아이디 찾기 기능입니다.") + @GetMapping("/find-id") + public ApiResponse findId(@RequestParam String name, @RequestParam String email) { + return ApiResponse.onSuccess(UserConverter.toFindIdResultDTO(authQueryService.findId(name, email))); + } + + @Operation(summary = "비밀번호 찾기", description = "비밀번호 찾기 기능입니다.") + @PostMapping("/find-password") + public ApiResponse findPassword(@RequestBody @Valid AuthRequestDTO.FindPasswordDTO request) { + authCommandService.findPassword(request); + return ApiResponse.onSuccess("인증 메일이 성공적으로 전송되었습니다."); + } + + @Operation(summary = "비밀번호 찾기 코드 인증", description = "비밀번호 찾기 시 이메일 코드 인증 기능입니다.") + @PostMapping("/verify-password-code") + public ApiResponse verifyPasswordCode(@RequestBody @Valid AuthRequestDTO.VerifyCodeDTO request) { + return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request, "findPassword"))); + } } diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index 90be4ff4..ec5a4078 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -41,6 +41,7 @@ public static class SignupDTO { public static class LoginDTO { @NotNull String userId; + @NotNull String password; } @@ -67,10 +68,20 @@ public static class VerifyEmailDTO { public static class VerifyCodeDTO { @NotNull String code; + @NotNull String email; } + @Getter + public static class FindPasswordDTO { + @NotNull + String name; + + @NotNull + String userId; + } + @Getter public static class SocialLoginDTO { @NotNull diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java index b1935af0..877db214 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java @@ -51,4 +51,12 @@ public static class CheckIdResultDTO { public static class VerifyCodeResultDTO { boolean isVerified; } + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class FindIdResultDTO { + String userId; + } } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java index 181eea5d..9d0a6e24 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -4,16 +4,15 @@ import com.otakumap.domain.auth.dto.AuthResponseDTO; import com.otakumap.domain.auth.jwt.dto.JwtDTO; import com.otakumap.domain.user.entity.User; -import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletRequest; public interface AuthCommandService { User signup(AuthRequestDTO.SignupDTO request); AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request); - boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request); boolean checkId(AuthRequestDTO.CheckIdDTO request); - void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; - boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); + void verifyEmail(AuthRequestDTO.VerifyEmailDTO request); + boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request, String requestType); JwtDTO reissueToken(String refreshToken); void logout(HttpServletRequest request); + void findPassword(AuthRequestDTO.FindPasswordDTO request); } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index 5ca06fe3..eb4d62c8 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -57,31 +57,25 @@ public AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request) { return UserConverter.toLoginResultDTO(user, accessToken, refreshToken); } - @Override - public boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request) { - return userRepository.existsByNickname(request.getNickname()); - } - @Override public boolean checkId(AuthRequestDTO.CheckIdDTO request) { return userRepository.existsByUserId(request.getUserId()); } @Override - public void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { + public void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) { try { - if(userRepository.existsByEmail(request.getEmail())) { - throw new AuthHandler(ErrorStatus.EMAIL_ALREADY_EXISTS); - } - mailService.sendEmail(request.getEmail()); + mailService.sendEmail(request.getEmail(), "signup"); + } catch (MessagingException e) { + throw new AuthHandler(ErrorStatus.EMAIL_WRITE_FAILED); } catch (MailException e) { throw new AuthHandler(ErrorStatus.EMAIL_SEND_FAILED); } } @Override - public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request) { - String authCode = (String) redisUtil.get("auth:" + request.getEmail()); + public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request, String requestType) { + String authCode = (String) redisUtil.get("auth:" + request.getEmail() + ":" + requestType); if (authCode == null) { throw new AuthHandler(ErrorStatus.EMAIL_CODE_EXPIRED); } @@ -116,4 +110,16 @@ public void logout(HttpServletRequest request) { throw new AuthHandler(ErrorStatus.TOKEN_EXPIRED); } } + + @Override + public void findPassword(AuthRequestDTO.FindPasswordDTO request) { + User user = userRepository.findByNameAndUserId(request.getName(), request.getUserId()).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + try { + mailService.sendEmail(user.getEmail(), "findPassword"); + } catch (MessagingException e) { + throw new AuthHandler(ErrorStatus.EMAIL_WRITE_FAILED); + } catch (MailException e) { + throw new AuthHandler(ErrorStatus.EMAIL_SEND_FAILED); + } + } } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java b/src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java new file mode 100644 index 00000000..ae6b8947 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.auth.service; + + +public interface AuthQueryService { + String findId(String name, String email); +} diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java new file mode 100644 index 00000000..2da5a7c3 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java @@ -0,0 +1,18 @@ +package com.otakumap.domain.auth.service; + +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthQueryServiceImpl implements AuthQueryService { + private final UserRepository userRepository; + + @Override + public String findId(String name, String email) { + return userRepository.findByNameAndEmail(name, email).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)).getUserId(); + } +} diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 1984a395..053f0fde 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -96,4 +96,10 @@ public static UserResponseDTO.UserInfoResponseDTO toUserInfoResponseDTO(User use .event_benefits_info(user.getIsEventBenefitsNotified()) .build(); } + + public static AuthResponseDTO.FindIdResultDTO toFindIdResultDTO(String userId) { + return AuthResponseDTO.FindIdResultDTO.builder() + .userId(userId) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java index d43fcf32..afa2492c 100644 --- a/src/main/java/com/otakumap/domain/user/repository/UserRepository.java +++ b/src/main/java/com/otakumap/domain/user/repository/UserRepository.java @@ -10,5 +10,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); boolean existsByNickname(String nickname); boolean existsByUserId(String userId); - boolean existsByEmail(String email); + Optional findByNameAndEmail(String name, String email); + Optional findByNameAndUserId(String name, String userId); } From f97ccb68e6b196a1cd7c84797347c4a6730acac9 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 12:40:12 +0900 Subject: [PATCH 109/516] =?UTF-8?q?Feat:=20=EB=A9=94=EC=9D=BC=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85,=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/MailService.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/service/MailService.java b/src/main/java/com/otakumap/domain/auth/service/MailService.java index fbf32301..813f9efe 100644 --- a/src/main/java/com/otakumap/domain/auth/service/MailService.java +++ b/src/main/java/com/otakumap/domain/auth/service/MailService.java @@ -5,7 +5,6 @@ import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.MailException; import org.springframework.mail.MailSendException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.scheduling.annotation.Async; @@ -39,39 +38,57 @@ private String createCode() { } // 이메일 폼 생성 - private MimeMessage createEmailForm(String email, String code) throws MessagingException { + private MimeMessage createEmailForm(String email, String code, String requestType) throws MessagingException { MimeMessage message = javaMailSender.createMimeMessage(); message.setFrom(senderEmail); message.addRecipients(MimeMessage.RecipientType.TO, email); - message.setSubject("오타쿠맵 이메일 인증 안내"); + String subject; String body = ""; - body += "

회원가입을 위한 이메일 인증을 진행합니다.

"; - body += "

아래 발급된 인증번호를 입력하여 인증을 완료해주세요.

"; + + // 요청 유형에 따른 이메일 내용 설정 + if ("signup".equals(requestType)) { + subject = "오타쿠맵 이메일 인증 안내"; + body += "

회원가입을 위한 이메일 인증을 진행합니다.

"; + body += "

아래 발급된 인증번호를 입력하여 인증을 완료해주세요.

"; + } else if ("findPassword".equals(requestType)) { + subject = "오타쿠맵 비밀번호 재설정 안내"; + body += "

비밀번호 찾기 요청을 받았습니다.

"; + body += "

아래 발급된 인증번호를 입력하여 비밀번호를 재설정해주세요.

"; + } else { + throw new MessagingException("Invalid request type."); + } + + message.setSubject(subject); + body += "
"; body += "

인증번호 " + code + "

"; message.setText(body, "UTF-8", "html"); // Redis에 해당 인증코드 인증 시간 설정(30분) - redisUtil.set("auth:" + email, code); - redisUtil.expire("auth:" + email, 30 * 60 * 1000L, TimeUnit.MILLISECONDS); + String redisKey = "auth:" + email + ":" + requestType; // signup or findPassword 구분 + redisUtil.set(redisKey, code); + redisUtil.expire(redisKey, 30 * 60 * 1000L, TimeUnit.MILLISECONDS); return message; } // 메일 발송 @Async - public void sendEmail(String sendEmail) throws MessagingException { - if (redisUtil.exists("auth:" + sendEmail)) { - redisUtil.delete("auth:" + sendEmail); + public void sendEmail(String sendEmail, String requestType) throws MessagingException { + String redisKey = "auth:" + sendEmail + ":" + requestType; + if (redisUtil.exists(redisKey)) { + redisUtil.delete(redisKey); } String authCode = createCode(); - MimeMessage message = createEmailForm(sendEmail, authCode); // 메일 생성 try { + MimeMessage message = createEmailForm(sendEmail, authCode, requestType); // 메일 생성 javaMailSender.send(message); // 메일 발송 - } catch (MailException e) { + } catch (MailSendException e) { throw new MailSendException(e.getMessage()); + } catch (MessagingException e) { + throw new MessagingException(e.getMessage()); } } } From ad686e4c036db5c31483b984715bb0eada0f6416 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 12:40:24 +0900 Subject: [PATCH 110/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/global/apiPayload/code/status/ErrorStatus.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index beaaca29..ced2a9da 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -21,6 +21,7 @@ public enum ErrorStatus implements BaseErrorCode { EMAIL_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "AUTH4002", "인증 코드가 만료되었습니다. 다시 요청해주세요."), CODE_NOT_EQUAL(HttpStatus.BAD_REQUEST, "AUTH4003", "인증 코드가 올바르지 않습니다."), EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AUTH5001", "메일 발송 중 오류가 발생했습니다."), + EMAIL_WRITE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AUTH5002", "메일 작성 중 문제가 발생했습니다."), EMAIL_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "AUTH4004", "이미 사용 중인 이메일입니다."), INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH4005", "유효하지 않은 토큰입니다."), TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH4006", "토큰이 만료되었습니다."), From 36a72b683d0843b524f290d597486597308ebcf3 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 12:41:29 +0900 Subject: [PATCH 111/516] =?UTF-8?q?Feat:=20isFavorite(=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/place/dto/PlaceResponseDTO.java | 4 +++- src/main/java/com/otakumap/domain/place/entity/Place.java | 5 +++++ .../java/com/otakumap/domain/place/service/PlaceService.java | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java index 674810ac..ca669f90 100644 --- a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java @@ -9,7 +9,9 @@ public record PlaceResponseDTO( Double lat, Double lng, String description, - LocalDateTime savedAt + LocalDateTime savedAt, + + Boolean isFavorite ) { // 필요한 경우, 추가적인 메서드를 여기에 정의할 수 있습니다. } diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 277be961..e3869c03 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -6,6 +6,7 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.ColumnDefault; import java.time.LocalDateTime; import java.util.ArrayList; @@ -37,6 +38,10 @@ public class Place extends BaseEntity { private LocalDateTime savedAt; + @Column(name = "is_favorite", nullable = false) + @ColumnDefault("false") + private Boolean isFavorite; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceService.java b/src/main/java/com/otakumap/domain/place/service/PlaceService.java index 5a54c499..01ae5bb9 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceService.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceService.java @@ -22,7 +22,8 @@ public List getSavedPlaces(Long userId) { place.getLat(), place.getLng(), place.getDescription(), - place.getSavedAt() + place.getSavedAt(), + place.getIsFavorite() )) .collect(Collectors.toList()); } From 9d14abe1da01697ffb8c0f75099d73480c108c03 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 12:45:31 +0900 Subject: [PATCH 112/516] =?UTF-8?q?Feat:=20Service=EC=97=90=EC=84=9C=20Pla?= =?UTF-8?q?ceResponseDTO=EC=97=90=EC=84=9C=20=EC=B9=BC=EB=9F=BC=20?= =?UTF-8?q?=EA=B0=92=20=EA=B0=96=EA=B3=A0=20=EC=98=A4=EB=8A=94=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=EC=9D=84=20converter=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/converter/PlaceConverter.java | 20 +++++++++++++++++++ .../domain/place/service/PlaceService.java | 11 ++-------- 2 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java new file mode 100644 index 00000000..d49b71f8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.place.converter; + +import com.otakumap.domain.place.dto.PlaceResponseDTO; +import com.otakumap.domain.place.entity.Place; +import org.springframework.stereotype.Component; + +@Component +public class PlaceConverter { + public static PlaceResponseDTO convert(Place place) { + return new PlaceResponseDTO( + place.getId(), + place.getName(), + place.getLat(), + place.getLng(), + place.getDescription(), + place.getSavedAt(), + place.getIsFavorite() + ); + } +} diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceService.java b/src/main/java/com/otakumap/domain/place/service/PlaceService.java index 01ae5bb9..05ae8507 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceService.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceService.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.service; +import com.otakumap.domain.place.converter.PlaceConverter; import com.otakumap.domain.place.dto.PlaceResponseDTO; import com.otakumap.domain.place.repository.PlaceRepository; import lombok.RequiredArgsConstructor; @@ -16,15 +17,7 @@ public class PlaceService { public List getSavedPlaces(Long userId) { return placeRepository.findByUserId(userId).stream() - .map(place -> new PlaceResponseDTO( - place.getId(), - place.getName(), - place.getLat(), - place.getLng(), - place.getDescription(), - place.getSavedAt(), - place.getIsFavorite() - )) + .map(PlaceConverter::convert) .collect(Collectors.toList()); } From fedd4bef57941ff35d099a6fa4a8312e87ff9cf6 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:47:19 +0900 Subject: [PATCH 113/516] =?UTF-8?q?Refactor:=20RouteLikeController=20saveR?= =?UTF-8?q?outeLike=20URL=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/controller/RouteLikeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index d3fb5f6a..297a5cf8 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -23,7 +23,7 @@ public class RouteLikeController { private final RouteLikeCommandService routeLikeCommandService; @Operation(summary = "루트 저장", description = "루트를 저장합니다.") - @PostMapping("/routes/{routeId}/save") + @PostMapping("/routes/{routeId}") @Parameters({ @Parameter(name = "routeId", description = "루트 Id") }) From c5f4af46e9a600ba425e738cd280267b1d9cbdf6 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:51:24 +0900 Subject: [PATCH 114/516] =?UTF-8?q?Refactor:=20RouteLike=20Enum=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/route_like/entity/RouteLike.java | 5 ----- .../java/com/otakumap/domain/route_like/enums/Status.java | 6 ------ .../route_like/service/RouteLikeCommandServiceImpl.java | 2 -- 3 files changed, 13 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/route_like/enums/Status.java diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index 450ea977..f7f53fdb 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -1,7 +1,6 @@ package com.otakumap.domain.route_like.entity; import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_like.enums.Status; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -35,8 +34,4 @@ public class RouteLike extends BaseEntity { @Column(name = "is_favorite", nullable = false) @ColumnDefault("false") private Boolean isFavorite; - - @Enumerated(EnumType.STRING) - @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) - private Status status; } diff --git a/src/main/java/com/otakumap/domain/route_like/enums/Status.java b/src/main/java/com/otakumap/domain/route_like/enums/Status.java deleted file mode 100644 index 4727849c..00000000 --- a/src/main/java/com/otakumap/domain/route_like/enums/Status.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.otakumap.domain.route_like.enums; - -public enum Status { - ACTIVE, - INACTIVE -} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index c3c4fd39..cc69e289 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -3,7 +3,6 @@ import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_like.entity.RouteLike; -import com.otakumap.domain.route_like.enums.Status; import com.otakumap.domain.route_like.repository.RouteLikeRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; @@ -32,7 +31,6 @@ public void saveRouteLike(User user, Long routeId) { RouteLike routeLike = RouteLike.builder() .user(user) .route(route) - .status(Status.ACTIVE) .isFavorite(Boolean.TRUE) .build(); From 8cf22af97d8369083da4c771d127671e49eee6df Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:56:27 +0900 Subject: [PATCH 115/516] =?UTF-8?q?Feat:=20RouteLikeConverter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route_like/converter/RouteLikeConverter.java | 16 ++++++++++++++++ .../service/RouteLikeCommandServiceImpl.java | 7 ++----- 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java new file mode 100644 index 00000000..c97b01fa --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.route_like.converter; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.domain.user.entity.User; + +public class RouteLikeConverter { + + public static RouteLike toRouteLike(User user, Route route) { + return RouteLike.builder() + .user(user) + .route(route) + .isFavorite(Boolean.TRUE) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index cc69e289..48d2f7ee 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -2,6 +2,7 @@ import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_like.converter.RouteLikeConverter; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.route_like.repository.RouteLikeRepository; import com.otakumap.domain.user.entity.User; @@ -28,11 +29,7 @@ public void saveRouteLike(User user, Long routeId) { throw new RouteHandler(ErrorStatus.ROUTE_LIKE_ALREADY_EXISTS); } - RouteLike routeLike = RouteLike.builder() - .user(user) - .route(route) - .isFavorite(Boolean.TRUE) - .build(); + RouteLike routeLike = RouteLikeConverter.toRouteLike(user, route); routeLikeRepository.save(routeLike); } From 23770a09890506cb45e0c7cc0bdd9f69cf055f4b Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:00:22 +0900 Subject: [PATCH 116/516] =?UTF-8?q?Refactor:=20Route,=20User=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/route/entity/Route.java | 2 +- src/main/java/com/otakumap/domain/user/entity/User.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java index cce700fe..18278ebf 100644 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -22,7 +22,7 @@ public class Route extends BaseEntity { @Column(length = 50, nullable = false) private String name; - @OneToMany(mappedBy = "route") + @OneToMany(mappedBy = "route", cascade = CascadeType.ALL) private List routeLikes; @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 5194fc35..25637828 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -71,8 +71,8 @@ public class User extends BaseEntity { @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; - @OneToMany(mappedBy = "user") - private List routeLikes; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List routeLikes = new ArrayList<>(); @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List placeLikes = new ArrayList<>(); From ba914e5bc0410b2020647282517590cbea54a8d7 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:01:34 +0900 Subject: [PATCH 117/516] =?UTF-8?q?Refactor:=20@Validated=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/controller/RouteLikeController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 297a5cf8..4f324267 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -8,7 +8,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,7 +16,6 @@ @RestController @RequestMapping("/api") @RequiredArgsConstructor -@Validated public class RouteLikeController { private final RouteLikeCommandService routeLikeCommandService; From fa85226556a0cc0fc45b4fc9c501e7ad001c6cb6 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:09:53 +0900 Subject: [PATCH 118/516] =?UTF-8?q?Refactor:=20EventReview=EC=99=80=20Plac?= =?UTF-8?q?eReview=EC=97=90=20route=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/event_review/entity/EventReview.java | 5 ----- .../com/otakumap/domain/place_review/entity/PlaceReview.java | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index d06d73a8..c149f86d 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -2,7 +2,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -46,8 +45,4 @@ public class EventReview extends BaseEntity { @JoinColumn(name = "event_id") private Event event; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "route_id", referencedColumnName = "id") - private Route route; - } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 8eba96c3..e7fd4ead 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -2,7 +2,6 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -43,7 +42,4 @@ public class PlaceReview extends BaseEntity { @JoinColumn(name = "place_id", nullable = false) private Place place; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "route_id", referencedColumnName = "id") - private Route route; } From a68790ffdbb33441a9de539bc6f9e1aa175e8fdb Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 13:16:02 +0900 Subject: [PATCH 119/516] =?UTF-8?q?Feat:=20=EC=B9=BC=EB=9F=BC=EB=AA=85=20d?= =?UTF-8?q?escription=20->=20detail=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(erd?= =?UTF-8?q?=20=EB=AC=B8=EC=84=9C=EC=97=90=20=EB=A7=9E=EC=B6=B0=EC=84=9C..)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place/converter/PlaceConverter.java | 2 +- src/main/java/com/otakumap/domain/place/entity/Place.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index d49b71f8..74a5605a 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -12,7 +12,7 @@ public static PlaceResponseDTO convert(Place place) { place.getName(), place.getLat(), place.getLng(), - place.getDescription(), + place.getDetail(), place.getSavedAt(), place.getIsFavorite() ); diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index e3869c03..297d9a4d 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -33,8 +33,8 @@ public class Place extends BaseEntity { @Column(nullable = false) private Double lng; - @Column(nullable = false, length = 100) - private String description; + @Column(name = "detail", nullable = false, length = 100) + private String detail; private LocalDateTime savedAt; From afe0beaf9a517c8260c45779c73738104a05d4fa Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:19:20 +0900 Subject: [PATCH 120/516] =?UTF-8?q?Feat:=20RouteItem=EC=97=90=20itemId=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route_item/entity/RouteItem.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 08ee6a80..8258363f 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -24,6 +24,9 @@ public class RouteItem extends BaseEntity { @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) private ItemType itemType; + @Column(nullable = false) + private Long itemId; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "route_id") private Route route; From 356e02a0dff604802301875c7b98f84dff272616 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 13:48:44 +0900 Subject: [PATCH 121/516] =?UTF-8?q?Feat:=20=EB=82=B4=EA=B0=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=9C=20=ED=9B=84=EA=B8=B0=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceReviewRepository.java | 3 ++ .../domain/user/contoller/UserController.java | 16 ++++++++++ .../domain/user/converter/UserConverter.java | 29 +++++++++++++++++++ .../domain/user/dto/UserResponseDTO.java | 29 +++++++++++++++++++ .../domain/user/service/UserQueryService.java | 3 ++ .../user/service/UserQueryServiceImpl.java | 10 +++++++ .../validation/annotation/CheckPage.java | 17 +++++++++++ .../validator/CheckPageValidator.java | 20 +++++++++++++ 8 files changed, 127 insertions(+) create mode 100644 src/main/java/com/otakumap/global/validation/annotation/CheckPage.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/CheckPageValidator.java diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 092cc8e6..a0970684 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -1,7 +1,10 @@ package com.otakumap.domain.place_review.repository; import com.otakumap.domain.place_review.entity.PlaceReview; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; public interface PlaceReviewRepository extends JpaRepository { + Page findAllByUserId(Long userId, PageRequest pageRequest); } diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index f0b0743b..4779fd86 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -1,6 +1,7 @@ package com.otakumap.domain.user.contoller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.dto.UserResponseDTO; @@ -8,9 +9,13 @@ import com.otakumap.domain.user.service.UserCommandService; import com.otakumap.domain.user.service.UserQueryService; import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.CheckPage; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; @RestController @@ -43,4 +48,15 @@ public ApiResponse reportEvent( userCommandService.reportEvent(request); return ApiResponse.onSuccess("이벤트 제보가 성공적으로 전송되었습니다."); } + + @GetMapping("/my-reviews") + @Operation(summary = "내가 작성한 리뷰 조회 API", description = "내가 작성한 리뷰를 조회합니다.") + @Parameters({ + @Parameter(name = "page", description = "페이지 번호 쿼리스트링입니다! (1~)") + }) + public ApiResponse getMyReviews( + @CurrentUser User user, @CheckPage @RequestParam(name = "page") Integer page) { + Page reviews = userQueryService.getMyReviews(user, page); + return ApiResponse.onSuccess(UserConverter.reviewListDTO(reviews)); + } } diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 1984a395..e04bd790 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -1,14 +1,18 @@ package com.otakumap.domain.user.converter; import com.otakumap.domain.auth.dto.*; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; import com.otakumap.global.util.UuidGenerator; +import org.springframework.data.domain.Page; import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; public class UserConverter { public static User toUser(AuthRequestDTO.SignupDTO request) { @@ -96,4 +100,29 @@ public static UserResponseDTO.UserInfoResponseDTO toUserInfoResponseDTO(User use .event_benefits_info(user.getIsEventBenefitsNotified()) .build(); } + + public static UserResponseDTO.UserReviewDTO reviewDTO(PlaceReview review) { + return UserResponseDTO.UserReviewDTO.builder() + .reviewId(review.getId()) + .title(review.getTitle()) + .content(review.getContent()) + .thumbnail(review.getImage() == null ? null : review.getImage().getFileUrl()) // 첫 이미지만 + .views(review.getView()) + .createdAt(review.getCreatedAt().toLocalDate()) + .build(); + } + + public static UserResponseDTO.UserReviewListDTO reviewListDTO(Page reviews) { + List userReviewDTOS = reviews.stream() + .map(UserConverter::reviewDTO).collect(Collectors.toList()); + + return UserResponseDTO.UserReviewListDTO.builder() + .reviews(userReviewDTOS) + .listSize(userReviewDTOS.size()) + .totalPages(reviews.getTotalPages()) + .totalElements(reviews.getTotalElements()) + .isFirst(reviews.isFirst()) + .isLast(reviews.isLast()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java index 29252a95..4d4a816a 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java @@ -5,6 +5,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; +import java.util.List; + public class UserResponseDTO { @Builder @Getter @@ -18,4 +21,30 @@ public static class UserInfoResponseDTO { private boolean community_activity; private boolean event_benefits_info; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class UserReviewListDTO { + List reviews; + Integer listSize; + Integer totalPages; + Long totalElements; + boolean isFirst; + boolean isLast; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class UserReviewDTO { + private Long reviewId; + private String title; + private String content; + private String thumbnail; + private Long views; + private LocalDate createdAt; + } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java index f7a37f2e..e42e36b2 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java @@ -1,8 +1,11 @@ package com.otakumap.domain.user.service; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Page; public interface UserQueryService { User getUserByEmail(String email); User getUserInfo(Long userId); + Page getMyReviews(User user, Integer page); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index d884ddeb..1220c4ef 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -1,16 +1,21 @@ package com.otakumap.domain.user.service; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.AuthHandler; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class UserQueryServiceImpl implements UserQueryService { private final UserRepository userRepository; + private final PlaceReviewRepository placeReviewRepository; @Override public User getUserByEmail(String email) { @@ -21,4 +26,9 @@ public User getUserByEmail(String email) { public User getUserInfo(Long userId) { return userRepository.findById(userId).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); } + + @Override + public Page getMyReviews(User user, Integer page) { + return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 10)); + } } diff --git a/src/main/java/com/otakumap/global/validation/annotation/CheckPage.java b/src/main/java/com/otakumap/global/validation/annotation/CheckPage.java new file mode 100644 index 00000000..b41e83ad --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/CheckPage.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.CheckPageValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = CheckPageValidator.class) +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckPage { + String message() default "Invalid page number"; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/validator/CheckPageValidator.java b/src/main/java/com/otakumap/global/validation/validator/CheckPageValidator.java new file mode 100644 index 00000000..2a421612 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/CheckPageValidator.java @@ -0,0 +1,20 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.global.validation.annotation.CheckPage; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class CheckPageValidator implements ConstraintValidator { + + @Override + public boolean isValid(Integer value, ConstraintValidatorContext context) { + if (value == null || value < 1) { + return false; + } + return true; + } + + @Override + public void initialize(CheckPage constraintAnnotation) { + } +} \ No newline at end of file From 2dd44ab56eb5cbbc051a5e4925feb8bb226b3480 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 13:53:41 +0900 Subject: [PATCH 122/516] =?UTF-8?q?Feat:=20docker-compose.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index de93935f..435fac97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,28 @@ ---- version: '3.8' services: + app: + image: otakumap/otaku_sb + container_name: otaku_sb-container + restart: always + ports: + - "8080:8080" + volumes: + - ./src/main/resources/application.yml:/app/src/main/resources/application.yml + networks: + - app_network redis: image: redis:latest container_name: redis ports: - - "6379:6379" \ No newline at end of file + - "6379:6379" + volumes: + - redis-data:/data + networks: + - app_network +networks: + app_network: + driver: bridge + +volumes: + redis-data: + driver: local \ No newline at end of file From 5c18b28c4dcc794ca21500f60b595da1a88370ce Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 13:54:15 +0900 Subject: [PATCH 123/516] =?UTF-8?q?Feat:=20Dockerfile=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..070774f4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17-jdk +ARG JAR_FILE=./build/libs/*-SNAPSHOT.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT [ "java", "-jar", "/app.jar" ] \ No newline at end of file From ee2496caf98b7f41ec6b4072c5682b6497d52961 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 13:54:29 +0900 Subject: [PATCH 124/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..b0c15d00 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,78 @@ +name: CI/CD using github actions & docker + +# event trigger +# dev 브랜치에 push가 되었을 때 실행 +on: + push: + branches: [ "dev" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + ## JDK setting - github actions에서 사용할 JDK 설정 + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + ## gradle caching - 빌드 시간 향상 + - name: Gradle Caching + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + ## gradlew 파일에 실행 권한을 부여 + - name: Grant execute permission for gradlew + run: + chmod +x gradlew + + # gradle build + - name: Build with Gradle + run: ./gradlew build -x test + + ## yml 파일 생성 + - name: make application.yml + if: contains(github.ref, 'dev') + run: | + mkdir -p ./src/main/resources + cd ./src/main/resources + touch ./application.yml + echo "${{ secrets.APPLICATION_YML }}" > ./application.yml + shell: bash + + ## docker build & push to production + - name: Docker build & push to prod + if: contains(github.ref, 'dev') + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }} . + docker push ${{ secrets.DOCKER_REPO }} + + ## deploy to production + - name: Deploy to prod + uses: appleboy/ssh-action@v0.1.6 + id: deploy-prod + if: contains(github.ref, 'dev') + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + envs: GITHUB_SHA + port: 22 + script: | + sudo docker ps + sudo docker rm -f $(docker ps -qa) + sudo docker pull ${{ secrets.DOCKER_REPO }} + sudo docker-compose -f ./docker-compose.yml up -d + sudo docker image prune -f From 4334479f49e92b8165991a8395584dbe88f3a160 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 13:57:08 +0900 Subject: [PATCH 125/516] =?UTF-8?q?Refactor:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20size=203=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/user/service/UserQueryServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index 1220c4ef..15f20e0c 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -29,6 +29,6 @@ public User getUserInfo(Long userId) { @Override public Page getMyReviews(User user, Integer page) { - return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 10)); + return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 3)); } } From b79150c4b0d1f2535db0ebd467da3563be599680 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 14:12:03 +0900 Subject: [PATCH 126/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b0c15d00..8b65bb21 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -56,8 +56,8 @@ jobs: if: contains(github.ref, 'dev') run: | docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} - docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }} . - docker push ${{ secrets.DOCKER_REPO }} + docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest . + docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest ## deploy to production - name: Deploy to prod @@ -73,6 +73,6 @@ jobs: script: | sudo docker ps sudo docker rm -f $(docker ps -qa) - sudo docker pull ${{ secrets.DOCKER_REPO }} + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest sudo docker-compose -f ./docker-compose.yml up -d sudo docker image prune -f From a9149525a3b0f87e31899584b1cc2413a027b0b1 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 14:12:19 +0900 Subject: [PATCH 127/516] =?UTF-8?q?Feat:=20docker-compose.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 435fac97..026b5ca1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.8' services: app: - image: otakumap/otaku_sb + image: otakumap/otaku_sb:latest container_name: otaku_sb-container restart: always ports: From 74fb439d5f091918f1bbaf6c6c3587f1c2c8ff15 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 14:13:23 +0900 Subject: [PATCH 128/516] =?UTF-8?q?Feat:=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/user/contoller/UserController.java | 8 +++++--- .../otakumap/domain/user/converter/UserConverter.java | 2 +- .../otakumap/domain/user/service/UserQueryService.java | 2 +- .../domain/user/service/UserQueryServiceImpl.java | 9 +++++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index 4779fd86..c281e5ed 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -52,11 +52,13 @@ public ApiResponse reportEvent( @GetMapping("/my-reviews") @Operation(summary = "내가 작성한 리뷰 조회 API", description = "내가 작성한 리뷰를 조회합니다.") @Parameters({ - @Parameter(name = "page", description = "페이지 번호 쿼리스트링입니다! (1~)") + @Parameter(name = "page", description = "페이지 번호 쿼리스트링입니다! (1~)"), + @Parameter(name = "sort", description = "정렬 기준 쿼리스트링입니다! (createdAt, views)") }) public ApiResponse getMyReviews( - @CurrentUser User user, @CheckPage @RequestParam(name = "page") Integer page) { - Page reviews = userQueryService.getMyReviews(user, page); + @CurrentUser User user, @CheckPage @RequestParam(name = "page") Integer page, + @RequestParam(name = "sort", defaultValue = "createdAt") String sort) { + Page reviews = userQueryService.getMyReviews(user, page, sort); return ApiResponse.onSuccess(UserConverter.reviewListDTO(reviews)); } } diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index e04bd790..b3cd33f5 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -106,7 +106,7 @@ public static UserResponseDTO.UserReviewDTO reviewDTO(PlaceReview review) { .reviewId(review.getId()) .title(review.getTitle()) .content(review.getContent()) - .thumbnail(review.getImage() == null ? null : review.getImage().getFileUrl()) // 첫 이미지만 + .thumbnail(review.getImage() == null ? null : review.getImage().getFileUrl()) // 이미지 여러 개면 수정 .views(review.getView()) .createdAt(review.getCreatedAt().toLocalDate()) .build(); diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java index e42e36b2..dfe3246c 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java @@ -7,5 +7,5 @@ public interface UserQueryService { User getUserByEmail(String email); User getUserInfo(Long userId); - Page getMyReviews(User user, Integer page); + Page getMyReviews(User user, Integer page, String sort); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index 15f20e0c..cd9c17a5 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @Service @@ -28,7 +29,11 @@ public User getUserInfo(Long userId) { } @Override - public Page getMyReviews(User user, Integer page) { - return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 3)); + public Page getMyReviews(User user, Integer page, String sort) { + Sort sortOrder = Sort.by("createdAt").descending(); + if ("views".equals(sort)) { + sortOrder = Sort.by("view").descending(); + } + return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 3, sortOrder)); } } From b63aa961f97111f5a61ff9fcf56486e2b43b16d2 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 14:24:07 +0900 Subject: [PATCH 129/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A1=B0=ED=9A=8C=20API=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceLikeConverter.java | 3 +- .../place_like/dto/PlaceLikeResponseDTO.java | 3 +- .../repository/PlaceLikeRepository.java | 7 +++- .../service/PlaceLikeQueryServiceImpl.java | 41 ++++++++++--------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 6c9eb2e5..68174f20 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -11,9 +11,10 @@ public class PlaceLikeConverter { public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(PlaceLike placeLike) { return PlaceLikeResponseDTO.PlaceLikePreViewDTO.builder() .id(placeLike.getId()) - .userId(placeLike.getUser().getId()) .placeId(placeLike.getPlace().getId()) + .name(placeLike.getPlace().getName()) .isFavorite(placeLike.getIsFavorite()) + .detail(placeLike.getPlace().getDetail()) .build(); } diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 452540a5..dc0cb0c7 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -15,9 +15,10 @@ public class PlaceLikeResponseDTO { @AllArgsConstructor public static class PlaceLikePreViewDTO { Long id; - Long userId; Long placeId; + String name; Boolean isFavorite; + String detail; } @Builder diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index da8e2c34..ab6300c3 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -6,12 +6,15 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import java.net.ContentHandler; import java.time.LocalDateTime; import java.util.List; public interface PlaceLikeRepository extends JpaRepository { - Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); - Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); Page findByUserIdAndIdLessThanOrderByIdDesc(Long userId, Long lastId, Pageable pageable); + + Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); + + Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index 69ed4b87..8e0a9027 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.service; +import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.place_like.converter.PlaceLikeConverter; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; @@ -28,37 +29,39 @@ public class PlaceLikeQueryServiceImpl implements PlaceLikeQueryService { @Override public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(Long userId, Long lastId, int limit) { + + List result; + Pageable pageable = PageRequest.of(0, limit+1); User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); - Pageable pageable = PageRequest.of(0, limit+1); Page placeLikePage = placeLikeRepository.findByUserIdAndIdLessThanOrderByIdDesc(userId, lastId, pageable); - List placeLikeDTOs = placeLikePage.getContent().stream() - .map(placeLike -> new PlaceLikeResponseDTO.PlaceLikePreViewDTO( - placeLike.getId(), - placeLike.getUser().getId(), - placeLike.getPlace().getId(), - placeLike.getIsFavorite() - )) - .collect(Collectors.toList()); - - boolean hasNext = placeLikePage.hasNext(); - Long newLastId = placeLikeDTOs.isEmpty() ? null : placeLikeDTOs.get(placeLikeDTOs.size() - 1).getId(); - - return new PlaceLikeResponseDTO.PlaceLikePreViewListDTO(placeLikeDTOs, hasNext, newLastId); + if (lastId.equals(0L)) { + // lastId가 0일 경우: 처음부터 데이터를 조회 + result = placeLikeRepository.findAllByUserIsOrderByCreatedAtDesc(user, pageable).getContent(); + } else { + // lastId가 0이 아닌 경우: lastId를 기준으로 이전 데이터를 조회 + PlaceLike placeLike = placeLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + result = placeLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, placeLike.getCreatedAt(), pageable).getContent(); + } + return createPlaceLikePreviewListDTO(user, result, limit); } + private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(User user, List placeLikes, int limit) { + boolean hasNext = placeLikes.size() > limit; + Long lastId = null; + if (hasNext) { + placeLikes = placeLikes.subList(0, placeLikes.size() - 1); + lastId = placeLikes.get(placeLikes.size() - 1).getId(); + } - private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(User user, Page placeLikePage) { - List list = placeLikePage.getContent().stream() + List list = placeLikes + .stream() .map(PlaceLikeConverter::placeLikePreViewDTO) .collect(Collectors.toList()); - boolean hasNext = placeLikePage.hasNext(); - Long lastId = list.isEmpty() ? null : list.get(list.size() - 1).getId(); - return PlaceLikeConverter.placeLikePreViewListDTO(list, hasNext, lastId); } From dbd29925989b2a0ef5a988c7683712492f37af10 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 14:45:05 +0900 Subject: [PATCH 130/516] =?UTF-8?q?Feat:=20=EB=8F=99=EC=9D=BC=ED=95=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=88=98=EC=97=90=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=EC=88=9C=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/user/service/UserQueryServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index cd9c17a5..679a9ef2 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -30,9 +30,9 @@ public User getUserInfo(Long userId) { @Override public Page getMyReviews(User user, Integer page, String sort) { - Sort sortOrder = Sort.by("createdAt").descending(); + Sort sortOrder = Sort.by(Sort.Order.desc("createdAt")); if ("views".equals(sort)) { - sortOrder = Sort.by("view").descending(); + sortOrder = Sort.by(Sort.Order.desc("view"), Sort.Order.desc("createdAt")); } return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 3, sortOrder)); } From 01385033a77c6d995102f03d4a1f9a530ce2cab0 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 16:13:37 +0900 Subject: [PATCH 131/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b65bb21..53cd5143 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -74,5 +74,5 @@ jobs: sudo docker ps sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest - sudo docker-compose -f ./docker-compose.yml up -d + sudo docker-compose up -d sudo docker image prune -f From 1468ecf5960e96714f5ca00bf4c8639be8189017 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 16:20:04 +0900 Subject: [PATCH 132/516] =?UTF-8?q?Feat:=20=ED=95=84=EC=9A=94=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20API=20=EC=82=AD=EC=A0=9C=20(=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EB=90=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/controller/PlaceController.java | 11 +------- .../place/converter/PlaceConverter.java | 13 ---------- .../domain/place/dto/PlaceResponseDTO.java | 17 ------------- .../otakumap/domain/place/entity/Place.java | 4 --- .../place/repository/PlaceRepository.java | 3 --- .../domain/place/service/PlaceService.java | 25 ------------------- 6 files changed, 1 insertion(+), 72 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java delete mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceService.java diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java index fc036335..c7f13553 100644 --- a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -14,17 +14,8 @@ import java.util.List; @RestController -@RequestMapping("/api/map") +@RequestMapping("/api") @RequiredArgsConstructor public class PlaceController { - private final PlaceService placeService; - - @GetMapping("/saved-places") - @Operation(summary = "저장된 장소 조회 API", description = "현재 사용자가 저장한 장소 목록을 조회합니다.") - public ApiResponse> getSavedPlaces(@CurrentUser User user) { - // 인증된 사용자 정보를 기반으로 저장된 장소 조회 - List savedPlaces = placeService.getSavedPlaces(user.getId()); - return ApiResponse.onSuccess(savedPlaces); - } } diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index 74a5605a..6e1852d4 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -1,20 +1,7 @@ package com.otakumap.domain.place.converter; -import com.otakumap.domain.place.dto.PlaceResponseDTO; -import com.otakumap.domain.place.entity.Place; import org.springframework.stereotype.Component; @Component public class PlaceConverter { - public static PlaceResponseDTO convert(Place place) { - return new PlaceResponseDTO( - place.getId(), - place.getName(), - place.getLat(), - place.getLng(), - place.getDetail(), - place.getSavedAt(), - place.getIsFavorite() - ); - } } diff --git a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java deleted file mode 100644 index ca669f90..00000000 --- a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.otakumap.domain.place.dto; - -import java.time.LocalDateTime; - - -public record PlaceResponseDTO( - Long id, - String name, - Double lat, - Double lng, - String description, - LocalDateTime savedAt, - - Boolean isFavorite -) { - // 필요한 경우, 추가적인 메서드를 여기에 정의할 수 있습니다. -} diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 297d9a4d..84426bec 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -42,10 +42,6 @@ public class Place extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List reviews = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index ecdad675..46aa245a 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -7,7 +7,4 @@ import java.util.List; public interface PlaceRepository extends JpaRepository { - - // 특정 사용자 ID로 저장된 장소 조회 - List findByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceService.java b/src/main/java/com/otakumap/domain/place/service/PlaceService.java deleted file mode 100644 index 05ae8507..00000000 --- a/src/main/java/com/otakumap/domain/place/service/PlaceService.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.otakumap.domain.place.service; - -import com.otakumap.domain.place.converter.PlaceConverter; -import com.otakumap.domain.place.dto.PlaceResponseDTO; -import com.otakumap.domain.place.repository.PlaceRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class PlaceService { - - private final PlaceRepository placeRepository; - - public List getSavedPlaces(Long userId) { - return placeRepository.findByUserId(userId).stream() - .map(PlaceConverter::convert) - .collect(Collectors.toList()); - } - -} - From 7a1ec129a8d67243573b4473f0ecc94201845668 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 16:20:50 +0900 Subject: [PATCH 133/516] =?UTF-8?q?Feat:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/controller/PlaceController.java | 9 --------- .../java/com/otakumap/domain/place/entity/Place.java | 1 - .../domain/place/repository/PlaceRepository.java | 3 --- 3 files changed, 13 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java index c7f13553..306c5c6e 100644 --- a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -1,18 +1,9 @@ package com.otakumap.domain.place.controller; -import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.place.dto.PlaceResponseDTO; -import com.otakumap.domain.place.service.PlaceService; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequestMapping("/api") @RequiredArgsConstructor diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 84426bec..e6ae8e5a 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -2,7 +2,6 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; -import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 46aa245a..9807a873 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -3,8 +3,5 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Arrays; -import java.util.List; - public interface PlaceRepository extends JpaRepository { } From cbfe5a5d1b663729323578d05661d77f8d589764 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 16:23:53 +0900 Subject: [PATCH 134/516] =?UTF-8?q?Feat:=20docker-compose.yml,=20applicati?= =?UTF-8?q?on.yml=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 28 ------------ src/main/resources/application.yml | 68 ------------------------------ 2 files changed, 96 deletions(-) delete mode 100644 docker-compose.yml delete mode 100644 src/main/resources/application.yml diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 026b5ca1..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: '3.8' -services: - app: - image: otakumap/otaku_sb:latest - container_name: otaku_sb-container - restart: always - ports: - - "8080:8080" - volumes: - - ./src/main/resources/application.yml:/app/src/main/resources/application.yml - networks: - - app_network - redis: - image: redis:latest - container_name: redis - ports: - - "6379:6379" - volumes: - - redis-data:/data - networks: - - app_network -networks: - app_network: - driver: bridge - -volumes: - redis-data: - driver: local \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 596dc4d0..00000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,68 +0,0 @@ -spring: - datasource: - url: ${DB_URL} - username: ${DB_USER} - password: ${DB_PASSWORD} - driver-class-name: com.mysql.cj.jdbc.Driver - sql: - init: - mode: never - jpa: - properties: - hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect - show_sql: true - format_sql: true - use_sql_comments: true - hbm2ddl: - auto: update - default_batch_fetch_size: 1000 - mail: - host: ${MAIL_HOST} - port: ${MAIL_PORT} - username: ${MAIL_USERNAME} - password: ${MAIL_PASSWORD} - properties: - mail: - smtp: - auth: true - starttls: - enable: true - required: true - connectiontimeout: 5000 - timeout: 5000 - writetimeout: 5000 - auth-code-expiration-millis: 1800000 # 30 * 60 * 1000 == 30? - - data: - redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} - repositories: - enabled: false - - jwt: - secret: ${JWT_SECRET} - token: - access-expiration-time: 3600000 #1시간 - refresh-expiration-time: 604800000 #7일 - -social: - kakao: - client-id: ${KAKAO_CLIENT_ID} - client-secret: ${KAKAO_CLIENT_SECRET} - redirect-uri: ${KAKAO_REDIRECT_URI} - token-uri: ${KAKAO_TOKEN_URI} - user-info-uri: ${KAKAO_USERINFO_URI} - google: - client-id: ${GOOGLE_CLIENT_ID} - client-secret: ${GOOGLE_CLIENT_SECRET} - redirect-uri: ${GOOGLE_REDIRECT_URI} - token-uri: ${GOOGLE_TOKEN_URI} - user-info-uri: ${GOOGLE_USERINFO_URI} - naver: - client-id: ${NAVER_CLIENT_ID} - client-secret: ${NAVER_CLIENT_SECRET} - redirect-uri: ${NAVER_REDIRECT_URI} - token-uri: ${NAVER_TOKEN_URI} - user-info-uri: ${NAVER_USERINFO_URI} \ No newline at end of file From 10c94f8e8429154f26c3b15efdb7c252981d0494 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 21 Jan 2025 16:24:00 +0900 Subject: [PATCH 135/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5(=EC=B0=9C=ED=95=98=EA=B8=B0)=20api=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventLikeController.java | 12 ++++++++++++ .../service/EventLikeCommandService.java | 3 +++ .../service/EventLikeCommandServiceImpl.java | 17 +++++++++++++++++ .../service/EventLikeQueryService.java | 1 + 4 files changed, 33 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 755ee506..44276cb5 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -1,8 +1,10 @@ package com.otakumap.domain.event_like.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.service.EventLikeCommandService; import com.otakumap.domain.event_like.service.EventLikeQueryService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ExistEventLike; import io.swagger.v3.oas.annotations.Operation; @@ -22,6 +24,16 @@ public class EventLikeController { private final EventLikeQueryService eventLikeQueryService; private final EventLikeCommandService eventLikeCommandService; + @Operation(summary = "이벤트 저장(찜하기)", description = "이벤트를 저장(찜)합니다.") + @GetMapping("/events/{eventId}/save") + @Parameters({ + @Parameter(name = "eventId", description = "이벤트 ID") + }) + public ApiResponse postPlaceLike(@CurrentUser User user, @PathVariable Long eventId) { + eventLikeCommandService.addEventLike(user, eventId); + return ApiResponse.onSuccess("이벤트가 성공적으로 저장되었습니다."); + } + // 로그인 시 엔드포인트 변경 예정 @Operation(summary = "저장된 이벤트 목록 조회", description = "저장된 이벤트 목록을 불러옵니다.") @GetMapping( "/users/{userId}/saved-events") diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java index 75959971..c8e7502d 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java @@ -1,7 +1,10 @@ package com.otakumap.domain.event_like.service; +import com.otakumap.domain.user.entity.User; + import java.util.List; public interface EventLikeCommandService { + void addEventLike(User user, Long eventId); void deleteEventLike(List eventIds); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index 839d81e0..7a614e19 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -1,6 +1,9 @@ package com.otakumap.domain.event_like.service; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.event_like.repository.EventLikeRepository; +import com.otakumap.domain.user.entity.User; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -13,8 +16,22 @@ @Transactional public class EventLikeCommandServiceImpl implements EventLikeCommandService { private final EventLikeRepository eventLikeRepository; + private final EventRepository eventRepository; private final EntityManager entityManager; + @Override + public void addEventLike(User user, Long eventId) { + eventLikeRepository.save( + EventLike.builder() + .event(eventRepository.getReferenceById(eventId)) + .user(user) + .isFavorite(true) + .build() + ); + entityManager.flush(); + entityManager.clear(); + } + @Override public void deleteEventLike(List eventIds) { eventLikeRepository.deleteAllByIdInBatch(eventIds); diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java index 59630cda..e1731f38 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java @@ -1,6 +1,7 @@ package com.otakumap.domain.event_like.service; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.user.entity.User; public interface EventLikeQueryService { EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId, Integer type, Long lastId, int limit); From 78c27148c0555f9d80113ce98b79d2d520162cd6 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 21 Jan 2025 16:28:24 +0900 Subject: [PATCH 136/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_like/service/EventLikeCommandServiceImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index 7a614e19..16628f79 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -1,9 +1,12 @@ package com.otakumap.domain.event_like.service; +import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,9 +24,11 @@ public class EventLikeCommandServiceImpl implements EventLikeCommandService { @Override public void addEventLike(User user, Long eventId) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); eventLikeRepository.save( EventLike.builder() - .event(eventRepository.getReferenceById(eventId)) + .event(event) .user(user) .isFavorite(true) .build() From f546a38cd42f28ead3268b21eb3a5c581f53333c Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 16:36:51 +0900 Subject: [PATCH 137/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 53cd5143..c3a473de 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,7 +48,7 @@ jobs: mkdir -p ./src/main/resources cd ./src/main/resources touch ./application.yml - echo "${{ secrets.APPLICATION_YML }}" > ./application.yml + echo "${{ secrets.APPLICATION_YML }}" > ./application.yml shell: bash ## docker build & push to production From 4f35cd41a4e2897743ccf4036ea2c49571ef5a79 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 16:51:57 +0900 Subject: [PATCH 138/516] =?UTF-8?q?Refactor:=20Notification=20Enum=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/notification/entity/Notification.java | 4 +++- .../domain/notification/entity/enums/NotificationType.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java diff --git a/src/main/java/com/otakumap/domain/notification/entity/Notification.java b/src/main/java/com/otakumap/domain/notification/entity/Notification.java index d854e517..83d4bcfa 100644 --- a/src/main/java/com/otakumap/domain/notification/entity/Notification.java +++ b/src/main/java/com/otakumap/domain/notification/entity/Notification.java @@ -1,5 +1,6 @@ package com.otakumap.domain.notification.entity; +import com.otakumap.domain.notification.entity.enums.NotificationType; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -17,8 +18,9 @@ public class Notification extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Enumerated(EnumType.STRING) @Column(nullable = false) - private String type; + private NotificationType type; @Column(nullable = false, length = 255) private String message; diff --git a/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java b/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java new file mode 100644 index 00000000..36fa4d58 --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.notification.entity.enums; + +public enum NotificationType { + COMMUNITY_ACTIVITY, POST_SAVE, POST_SUPPORT, SERVICE_NOTICE +} From 868b3ee4f04d4e512b49b05a85da417a95cc2172 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 17:41:11 +0900 Subject: [PATCH 139/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c3a473de..8b320ec2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,8 +45,9 @@ jobs: - name: make application.yml if: contains(github.ref, 'dev') run: | - mkdir -p ./src/main/resources - cd ./src/main/resources + cd ./src/main + mkdir -p resources + cd ./resources touch ./application.yml echo "${{ secrets.APPLICATION_YML }}" > ./application.yml shell: bash From a4c11ffa5beec2394084407b9430cbc902461c92 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 17:43:00 +0900 Subject: [PATCH 140/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b320ec2..46b4cf6a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,6 +45,7 @@ jobs: - name: make application.yml if: contains(github.ref, 'dev') run: | + echo "${{ secrets.APPLICATION_YML }}" cd ./src/main mkdir -p resources cd ./resources From 2ddb8b906966c48455884d670c690ba9590bd337 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 17:47:36 +0900 Subject: [PATCH 141/516] =?UTF-8?q?Feat:=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 30 ++++++++++++++++++ .../converter/NotificationConverter.java | 25 +++++++++++++++ .../dto/NotificationResponseDTO.java | 31 +++++++++++++++++++ .../repository/NotificationRepository.java | 3 ++ .../service/NotificationQueryService.java | 9 ++++++ .../service/NotificationQueryServiceImpl.java | 21 +++++++++++++ 6 files changed, 119 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/notification/controller/NotificationController.java create mode 100644 src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java create mode 100644 src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java create mode 100644 src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java b/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java new file mode 100644 index 00000000..435d112b --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.notification.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.notification.converter.NotificationConverter; +import com.otakumap.domain.notification.dto.NotificationResponseDTO; +import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.notification.service.NotificationQueryService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/notifications") +public class NotificationController { + private final NotificationQueryService notificationQueryService; + + @GetMapping + @Operation(summary = "알림 목록 조회 API", description = "알림 목록을 조회합니다.") + public ApiResponse getNotifications(@CurrentUser User user) { + List notifications = notificationQueryService.getNotifications(user.getId()); + return ApiResponse.onSuccess(NotificationConverter.notificationListDTO(notifications)); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java b/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java new file mode 100644 index 00000000..d5c0e3f5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java @@ -0,0 +1,25 @@ +package com.otakumap.domain.notification.converter; + +import com.otakumap.domain.notification.dto.NotificationResponseDTO; +import com.otakumap.domain.notification.entity.Notification; + +import java.util.List; +import java.util.stream.Collectors; + +public class NotificationConverter { + public static NotificationResponseDTO.NotificationDTO notificationDTO(Notification notification) { + return NotificationResponseDTO.NotificationDTO.builder() + .id(notification.getId()) + .type(notification.getType().name()) + .message(notification.getMessage()) + .createdAt(notification.getCreatedAt()) + .isRead(notification.isRead()) + .build(); + } + + public static NotificationResponseDTO.NotificationListDTO notificationListDTO(List notifications) { + return NotificationResponseDTO.NotificationListDTO.builder() + .notifications(notifications.stream().map(NotificationConverter::notificationDTO).collect(Collectors.toList())) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java b/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java new file mode 100644 index 00000000..1fb8f9d1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java @@ -0,0 +1,31 @@ +package com.otakumap.domain.notification.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class NotificationResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class NotificationListDTO { + List notifications; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class NotificationDTO { + Long id; + String type; + String message; + LocalDateTime createdAt; + boolean isRead; + } +} diff --git a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java index 739fada8..33df3896 100644 --- a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java +++ b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java @@ -3,5 +3,8 @@ import com.otakumap.domain.notification.entity.Notification; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface NotificationRepository extends JpaRepository { + List findAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java new file mode 100644 index 00000000..9b24a42e --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.notification.service; + +import com.otakumap.domain.notification.entity.Notification; + +import java.util.List; + +public interface NotificationQueryService { + List getNotifications(Long userId); +} diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java new file mode 100644 index 00000000..6792e767 --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.notification.service; + +import com.otakumap.domain.notification.dto.NotificationResponseDTO; +import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.notification.repository.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class NotificationQueryServiceImpl implements NotificationQueryService { + private final NotificationRepository notificationRepository; + + @Override + public List getNotifications(Long userId) { + List notifications = notificationRepository.findAllByUserId(userId); + return notifications; + } +} From c2d328f27fc5e4949d8cd0900d6e2c113c1e1585 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 21 Jan 2025 18:07:44 +0900 Subject: [PATCH 142/516] =?UTF-8?q?Feat:=20[=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A1=B0=ED=9A=8C]=20responseBody?= =?UTF-8?q?=EC=97=90=20=EC=BB=AC=EB=9F=BC=20=EB=8D=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_like/converter/PlaceLikeConverter.java | 16 +++++++++++++++- .../place_like/dto/PlaceLikeResponseDTO.java | 8 ++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 21031bc1..c90506f4 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -1,7 +1,9 @@ package com.otakumap.domain.place_like.converter; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; +import com.otakumap.domain.user.entity.User; import java.util.List; @@ -9,8 +11,12 @@ public class PlaceLikeConverter { public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(PlaceLike placeLike) { return PlaceLikeResponseDTO.PlaceLikePreViewDTO.builder() .id(placeLike.getId()) - .userId(placeLike.getUser().getId()) .placeId(placeLike.getPlace().getId()) + .name(placeLike.getPlace().getName()) + .detail(placeLike.getPlace().getDetail()) + .lat(placeLike.getPlace().getLat()) + .lng(placeLike.getPlace().getLng()) + .savedAt(placeLike.getPlace().getSavedAt()) .isFavorite(placeLike.getIsFavorite()) .build(); @@ -23,4 +29,12 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikePreViewListD .lastId(lastId) .build(); } + + public static PlaceLike toPlaceLike(User user, Place place) { + return PlaceLike.builder() + .user(user) + .place(place) + .isFavorite(Boolean.TRUE) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 452540a5..c504544b 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; public class PlaceLikeResponseDTO { @@ -15,8 +15,12 @@ public class PlaceLikeResponseDTO { @AllArgsConstructor public static class PlaceLikePreViewDTO { Long id; - Long userId; Long placeId; + String name; + String detail; + Double lat; + Double lng; + LocalDateTime savedAt; Boolean isFavorite; } From a843e5d281676f111bc194b8f2c1235875bcbd6e Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 18:17:36 +0900 Subject: [PATCH 143/516] =?UTF-8?q?Feat:=20=EC=9D=BD=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EC=95=8C=EB=A6=BC=EB=A7=8C=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 6 +- .../converter/NotificationConverter.java | 1 + .../dto/NotificationResponseDTO.java | 1 + .../repository/NotificationRepository.java | 2 +- .../service/NotificationQueryService.java | 2 +- .../service/NotificationQueryServiceImpl.java | 5 +- src/main/resources/application.yml | 68 +++++++++++++++++++ 7 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/application.yml diff --git a/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java b/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java index 435d112b..c811bbaf 100644 --- a/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java +++ b/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java @@ -22,9 +22,9 @@ public class NotificationController { private final NotificationQueryService notificationQueryService; @GetMapping - @Operation(summary = "알림 목록 조회 API", description = "알림 목록을 조회합니다.") - public ApiResponse getNotifications(@CurrentUser User user) { - List notifications = notificationQueryService.getNotifications(user.getId()); + @Operation(summary = "알림 목록 조회 API", description = "읽지 않은 알림 목록을 조회합니다.") + public ApiResponse getUnreadNotifications(@CurrentUser User user) { + List notifications = notificationQueryService.getUnreadNotifications(user.getId()); return ApiResponse.onSuccess(NotificationConverter.notificationListDTO(notifications)); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java b/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java index d5c0e3f5..9a0f89bc 100644 --- a/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java +++ b/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java @@ -12,6 +12,7 @@ public static NotificationResponseDTO.NotificationDTO notificationDTO(Notificati .id(notification.getId()) .type(notification.getType().name()) .message(notification.getMessage()) + .url(notification.getUrl()) .createdAt(notification.getCreatedAt()) .isRead(notification.isRead()) .build(); diff --git a/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java b/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java index 1fb8f9d1..cc74f931 100644 --- a/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/notification/dto/NotificationResponseDTO.java @@ -25,6 +25,7 @@ public static class NotificationDTO { Long id; String type; String message; + String url; LocalDateTime createdAt; boolean isRead; } diff --git a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java index 33df3896..802c1c38 100644 --- a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java +++ b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java @@ -6,5 +6,5 @@ import java.util.List; public interface NotificationRepository extends JpaRepository { - List findAllByUserId(Long userId); + List findAllByUserIdAndIsReadFalse(Long userId); } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java index 9b24a42e..0b00c5b9 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryService.java @@ -5,5 +5,5 @@ import java.util.List; public interface NotificationQueryService { - List getNotifications(Long userId); + List getUnreadNotifications(Long userId); } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java index 6792e767..c1206d7a 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java @@ -14,8 +14,7 @@ public class NotificationQueryServiceImpl implements NotificationQueryService { private final NotificationRepository notificationRepository; @Override - public List getNotifications(Long userId) { - List notifications = notificationRepository.findAllByUserId(userId); - return notifications; + public List getUnreadNotifications(Long userId) { + return notificationRepository.findAllByUserIdAndIsReadFalse(userId); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..596dc4d0 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,68 @@ +spring: + datasource: + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + sql: + init: + mode: never + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + show_sql: true + format_sql: true + use_sql_comments: true + hbm2ddl: + auto: update + default_batch_fetch_size: 1000 + mail: + host: ${MAIL_HOST} + port: ${MAIL_PORT} + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true + connectiontimeout: 5000 + timeout: 5000 + writetimeout: 5000 + auth-code-expiration-millis: 1800000 # 30 * 60 * 1000 == 30? + + data: + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} + repositories: + enabled: false + + jwt: + secret: ${JWT_SECRET} + token: + access-expiration-time: 3600000 #1시간 + refresh-expiration-time: 604800000 #7일 + +social: + kakao: + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + redirect-uri: ${KAKAO_REDIRECT_URI} + token-uri: ${KAKAO_TOKEN_URI} + user-info-uri: ${KAKAO_USERINFO_URI} + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + redirect-uri: ${GOOGLE_REDIRECT_URI} + token-uri: ${GOOGLE_TOKEN_URI} + user-info-uri: ${GOOGLE_USERINFO_URI} + naver: + client-id: ${NAVER_CLIENT_ID} + client-secret: ${NAVER_CLIENT_SECRET} + redirect-uri: ${NAVER_REDIRECT_URI} + token-uri: ${NAVER_TOKEN_URI} + user-info-uri: ${NAVER_USERINFO_URI} \ No newline at end of file From b3f8243d961178de1cf7e1dcc285317555183ab5 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 18:18:02 +0900 Subject: [PATCH 144/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 46b4cf6a..553b9734 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,7 +4,8 @@ name: CI/CD using github actions & docker # dev 브랜치에 push가 되었을 때 실행 on: push: - branches: [ "dev" ] + # branches: [ "dev" ] + branches: [ "ci/#50" ] permissions: contents: read @@ -51,6 +52,7 @@ jobs: cd ./resources touch ./application.yml echo "${{ secrets.APPLICATION_YML }}" > ./application.yml + cat ./src/main/resources/application.yml shell: bash ## docker build & push to production From 5809732814c7cc7b83a2ce8b4ef5a6eed288bb5a Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 21 Jan 2025 18:24:43 +0900 Subject: [PATCH 145/516] =?UTF-8?q?Feat:=20=EC=B5=9C=EC=8B=A0=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/repository/NotificationRepository.java | 3 ++- .../notification/service/NotificationQueryServiceImpl.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java index 802c1c38..e52ca697 100644 --- a/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java +++ b/src/main/java/com/otakumap/domain/notification/repository/NotificationRepository.java @@ -1,10 +1,11 @@ package com.otakumap.domain.notification.repository; import com.otakumap.domain.notification.entity.Notification; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface NotificationRepository extends JpaRepository { - List findAllByUserIdAndIsReadFalse(Long userId); + List findAllByUserIdAndIsReadFalse(Long userId, Sort sort); } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java index c1206d7a..98937c4e 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationQueryServiceImpl.java @@ -4,6 +4,7 @@ import com.otakumap.domain.notification.entity.Notification; import com.otakumap.domain.notification.repository.NotificationRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import java.util.List; @@ -15,6 +16,7 @@ public class NotificationQueryServiceImpl implements NotificationQueryService { @Override public List getUnreadNotifications(Long userId) { - return notificationRepository.findAllByUserIdAndIsReadFalse(userId); + Sort sort = Sort.by(Sort.Order.desc("createdAt")); + return notificationRepository.findAllByUserIdAndIsReadFalse(userId, sort); } } From 5a478fc943dab8138a57ca247ae25d3cb72f9c4f Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 19:42:58 +0900 Subject: [PATCH 146/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 553b9734..33b48967 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,20 +44,18 @@ jobs: ## yml 파일 생성 - name: make application.yml - if: contains(github.ref, 'dev') + if: contains(github.ref, 'ci/#50') run: | - echo "${{ secrets.APPLICATION_YML }}" cd ./src/main mkdir -p resources cd ./resources touch ./application.yml - echo "${{ secrets.APPLICATION_YML }}" > ./application.yml - cat ./src/main/resources/application.yml + echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml shell: bash ## docker build & push to production - name: Docker build & push to prod - if: contains(github.ref, 'dev') + if: contains(github.ref, 'ci/#50') run: | docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest . @@ -67,7 +65,7 @@ jobs: - name: Deploy to prod uses: appleboy/ssh-action@v0.1.6 id: deploy-prod - if: contains(github.ref, 'dev') + if: contains(github.ref, 'ci/#50') with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USERNAME }} From 04b8e936b091201d04d48e53078e8e69c5d33fc9 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 19:58:40 +0900 Subject: [PATCH 147/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 33b48967..3b3f4c85 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,6 +16,7 @@ jobs: steps: ## JDK setting - github actions에서 사용할 JDK 설정 - uses: actions/checkout@v3 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: @@ -46,9 +47,7 @@ jobs: - name: make application.yml if: contains(github.ref, 'ci/#50') run: | - cd ./src/main - mkdir -p resources - cd ./resources + cd ./src/main/resources touch ./application.yml echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml shell: bash @@ -73,7 +72,7 @@ jobs: envs: GITHUB_SHA port: 22 script: | - sudo docker ps + sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest sudo docker-compose up -d From c7a7f1d2185912ea04032e9be8675950946a093f Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 20:04:14 +0900 Subject: [PATCH 148/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3b3f4c85..85bae77e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,7 +47,9 @@ jobs: - name: make application.yml if: contains(github.ref, 'ci/#50') run: | - cd ./src/main/resources + cd ./src/main + mkdir -p resources + cd ./resources touch ./application.yml echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml shell: bash From 0da247b7a644766910444a16d4de06f529076fdc Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 20:30:31 +0900 Subject: [PATCH 149/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 85bae77e..f73cf72f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,15 +44,15 @@ jobs: run: ./gradlew build -x test ## yml 파일 생성 - - name: make application.yml - if: contains(github.ref, 'ci/#50') - run: | - cd ./src/main - mkdir -p resources - cd ./resources - touch ./application.yml - echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml - shell: bash +# - name: make application.yml +# if: contains(github.ref, 'ci/#50') +# run: | +# cd ./src/main +# mkdir -p resources +# cd ./resources +# touch ./application.yml +# echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml +# shell: bash ## docker build & push to production - name: Docker build & push to prod @@ -78,4 +78,4 @@ jobs: sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest sudo docker-compose up -d - sudo docker image prune -f + sudo docker image prune -f \ No newline at end of file From 86c23a7c36a9f676fbc88f615b3b2be229210da2 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 20:51:39 +0900 Subject: [PATCH 150/516] =?UTF-8?q?Fix:=20build.gradle=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 52782a9a..57ae7347 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,11 @@ repositories { mavenCentral() } +jar{ + enabled=false +} + + dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' From a2cacc24e3f409f40e78b1acb8048663804dfa28 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 20:57:07 +0900 Subject: [PATCH 151/516] =?UTF-8?q?Fix:=20build.gradle=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 57ae7347..095ad946 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From a0d00e9712a80e4817b69f7dc7f4728813396449 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 21:00:21 +0900 Subject: [PATCH 152/516] =?UTF-8?q?Fix:=20Dockerfile=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 7 ++++++- build.gradle | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 070774f4..b59549e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,9 @@ FROM openjdk:17-jdk ARG JAR_FILE=./build/libs/*-SNAPSHOT.jar +WORKDIR /app COPY ${JAR_FILE} app.jar -ENTRYPOINT [ "java", "-jar", "/app.jar" ] \ No newline at end of file + +# application.yml 복사 +COPY src/main/resources/application.yml /app/application.yml + +ENTRYPOINT [ "java", "-jar", "/app/app.jar" ] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 095ad946..57ae7347 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' - implementation 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From 2c2fbf04f79f39afa4c5d6ec60824829e5fcfec7 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 21:05:58 +0900 Subject: [PATCH 153/516] =?UTF-8?q?Fix:=20Dockerfile=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 18 +++++++++--------- Dockerfile | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f73cf72f..72bf6aa9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,15 +44,15 @@ jobs: run: ./gradlew build -x test ## yml 파일 생성 -# - name: make application.yml -# if: contains(github.ref, 'ci/#50') -# run: | -# cd ./src/main -# mkdir -p resources -# cd ./resources -# touch ./application.yml -# echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml -# shell: bash + - name: make application.yml + if: contains(github.ref, 'ci/#50') + run: | + cd ./src/main + mkdir -p resources + cd ./resources + touch ./application.yml + echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml + shell: bash ## docker build & push to production - name: Docker build & push to prod diff --git a/Dockerfile b/Dockerfile index b59549e5..72a71439 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,6 @@ WORKDIR /app COPY ${JAR_FILE} app.jar # application.yml 복사 -COPY src/main/resources/application.yml /app/application.yml +COPY ./src/main/resources/application.yml /app/application.yml ENTRYPOINT [ "java", "-jar", "/app/app.jar" ] \ No newline at end of file From 9418368390313a62550482b0f721483377d66efd Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 21:24:12 +0900 Subject: [PATCH 154/516] =?UTF-8?q?Fix:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 72bf6aa9..aeddc806 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,8 +4,7 @@ name: CI/CD using github actions & docker # dev 브랜치에 push가 되었을 때 실행 on: push: - # branches: [ "dev" ] - branches: [ "ci/#50" ] + branches: [ "dev" ] permissions: contents: read @@ -45,7 +44,7 @@ jobs: ## yml 파일 생성 - name: make application.yml - if: contains(github.ref, 'ci/#50') + if: contains(github.ref, 'dev') run: | cd ./src/main mkdir -p resources @@ -56,7 +55,7 @@ jobs: ## docker build & push to production - name: Docker build & push to prod - if: contains(github.ref, 'ci/#50') + if: contains(github.ref, 'dev') run: | docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest . @@ -66,7 +65,7 @@ jobs: - name: Deploy to prod uses: appleboy/ssh-action@v0.1.6 id: deploy-prod - if: contains(github.ref, 'ci/#50') + if: contains(github.ref, 'dev') with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USERNAME }} From e22a57a3fd7f7778a84b824f9bb430684d6127bc Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 21 Jan 2025 22:48:51 +0900 Subject: [PATCH 155/516] =?UTF-8?q?Feat:=20Swagger=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EC=97=90=20=ED=95=84=EC=88=98=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/auth/dto/AuthRequestDTO.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index ec5a4078..af5b7809 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -46,12 +46,6 @@ public static class LoginDTO { String password; } - @Getter - public static class CheckNicknameDTO { - @NotNull - String nickname; - } - @Getter public static class CheckIdDTO { @NotNull @@ -66,19 +60,19 @@ public static class VerifyEmailDTO { @Getter public static class VerifyCodeDTO { - @NotNull + @NotBlank(message = "인증 코드 입력은 필수입니다.") String code; - @NotNull + @NotBlank(message = "이메일 입력은 필수입니다.") String email; } @Getter public static class FindPasswordDTO { - @NotNull + @NotBlank(message = "이름 입력은 필수입니다.") String name; - @NotNull + @NotBlank(message = "아이디 입력은 필수입니다.") String userId; } From c63340be963770205e6796e47a5f82935f793f10 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 02:13:10 +0900 Subject: [PATCH 156/516] =?UTF-8?q?Chore:=20.gitIgnore=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c2065bc2..887564c1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + +src/main/resources From fd66708872f6a5e19f0a58381c75cb88528d315d Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 02:19:49 +0900 Subject: [PATCH 157/516] =?UTF-8?q?Revert=20"Feat:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20API=20=EA=B5=AC=ED=98=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 1 - .../place_review/entity/PlaceReview.java | 1 - .../otakumap/domain/route/entity/Route.java | 30 --------------- .../route/repository/RouteRepository.java | 8 ---- .../domain/route_item/entity/RouteItem.java | 33 ----------------- .../domain/route_item/enums/ItemType.java | 6 --- .../controller/RouteLikeController.java | 36 ------------------ .../converter/RouteLikeConverter.java | 16 -------- .../domain/route_like/entity/RouteLike.java | 37 ------------------- .../repository/RouteLikeRepository.java | 11 ------ .../service/RouteLikeCommandService.java | 7 ---- .../service/RouteLikeCommandServiceImpl.java | 36 ------------------ .../com/otakumap/domain/user/entity/User.java | 6 +-- .../apiPayload/code/status/ErrorStatus.java | 8 ---- .../exception/handler/RouteHandler.java | 10 ----- 15 files changed, 1 insertion(+), 245 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/route/entity/Route.java delete mode 100644 src/main/java/com/otakumap/domain/route/repository/RouteRepository.java delete mode 100644 src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java delete mode 100644 src/main/java/com/otakumap/domain/route_item/enums/ItemType.java delete mode 100644 src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java delete mode 100644 src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java delete mode 100644 src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java delete mode 100644 src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java delete mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java delete mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java delete mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index c149f86d..2ed19347 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -44,5 +44,4 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; - } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index e7fd4ead..41f3a0bb 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -41,5 +41,4 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id", nullable = false) private Place place; - } diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java deleted file mode 100644 index 18278ebf..00000000 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.otakumap.domain.route.entity; - -import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_like.entity.RouteLike; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -import java.util.List; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class Route extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(length = 50, nullable = false) - private String name; - - @OneToMany(mappedBy = "route", cascade = CascadeType.ALL) - private List routeLikes; - - @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) - private List routeItems; -} diff --git a/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java b/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java deleted file mode 100644 index 3714c8f0..00000000 --- a/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.otakumap.domain.route.repository; - -import com.otakumap.domain.route.entity.Route; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RouteRepository extends JpaRepository { - -} diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java deleted file mode 100644 index 8258363f..00000000 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.otakumap.domain.route_item.entity; - -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_item.enums.ItemType; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class RouteItem extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private Integer itemOrder; - - @Enumerated(EnumType.STRING) - @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) - private ItemType itemType; - - @Column(nullable = false) - private Long itemId; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "route_id") - private Route route; -} diff --git a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java deleted file mode 100644 index a6e11057..00000000 --- a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.otakumap.domain.route_item.enums; - -public enum ItemType { - PLACE, - EVENT -} diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java deleted file mode 100644 index 4f324267..00000000 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.otakumap.domain.route_like.controller; - -import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.route_like.service.RouteLikeCommandService; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api") -@RequiredArgsConstructor -public class RouteLikeController { - - private final RouteLikeCommandService routeLikeCommandService; - - @Operation(summary = "루트 저장", description = "루트를 저장합니다.") - @PostMapping("/routes/{routeId}") - @Parameters({ - @Parameter(name = "routeId", description = "루트 Id") - }) - public ApiResponse saveRouteLike(@PathVariable Long routeId, @CurrentUser User user) { - - routeLikeCommandService.saveRouteLike(user, routeId); - - return ApiResponse.onSuccess("루트가 성공적으로 저장되었습니다."); - } - - -} diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java deleted file mode 100644 index c97b01fa..00000000 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.otakumap.domain.route_like.converter; - -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_like.entity.RouteLike; -import com.otakumap.domain.user.entity.User; - -public class RouteLikeConverter { - - public static RouteLike toRouteLike(User user, Route route) { - return RouteLike.builder() - .user(user) - .route(route) - .isFavorite(Boolean.TRUE) - .build(); - } -} diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java deleted file mode 100644 index f7f53fdb..00000000 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.otakumap.domain.route_like.entity; - -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; -import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.DynamicInsert; -import org.hibernate.annotations.DynamicUpdate; - -@Entity -@Getter -@DynamicUpdate -@DynamicInsert -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -@Table(name = "route_like") -public class RouteLike extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @ManyToOne - @JoinColumn(name = "route_id") - private Route route; - - @Column(name = "is_favorite", nullable = false) - @ColumnDefault("false") - private Boolean isFavorite; -} diff --git a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java deleted file mode 100644 index b185c8df..00000000 --- a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.otakumap.domain.route_like.repository; - -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_like.entity.RouteLike; -import com.otakumap.domain.user.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RouteLikeRepository extends JpaRepository { - - boolean existsByUserAndRoute(User user, Route route); -} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java deleted file mode 100644 index 90bacef1..00000000 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.otakumap.domain.route_like.service; - -import com.otakumap.domain.user.entity.User; - -public interface RouteLikeCommandService { - void saveRouteLike(User user, Long routeId); -} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java deleted file mode 100644 index 48d2f7ee..00000000 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.otakumap.domain.route_like.service; - -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route.repository.RouteRepository; -import com.otakumap.domain.route_like.converter.RouteLikeConverter; -import com.otakumap.domain.route_like.entity.RouteLike; -import com.otakumap.domain.route_like.repository.RouteLikeRepository; -import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.RouteHandler; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { - - private final RouteLikeRepository routeLikeRepository; - private final RouteRepository routeRepository; - private final UserRepository userRepository; - @Override - public void saveRouteLike(User user, Long routeId) { - - Route route = routeRepository.findById(routeId) - .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); - - if(routeLikeRepository.existsByUserAndRoute(user, route)) { - throw new RouteHandler(ErrorStatus.ROUTE_LIKE_ALREADY_EXISTS); - } - - RouteLike routeLike = RouteLikeConverter.toRouteLike(user, route); - - routeLikeRepository.save(routeLike); - } -} diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 8280d7aa..c950ef47 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; - -import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; @@ -14,6 +12,7 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; + import java.util.ArrayList; import java.util.List; @@ -71,9 +70,6 @@ public class User extends BaseEntity { @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) - private List routeLikes = new ArrayList<>(); - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List placeLikes = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 422957ba..90f80f29 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -46,17 +46,9 @@ public enum ErrorStatus implements BaseErrorCode { // 후기 검색 관련 에러 REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), - - // 루트 관련 에러 - ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), - - // 루트 좋아요 관련 에러 - ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."); - // 알림 관련 에러 INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."); - private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java deleted file mode 100644 index cfb7f2f0..00000000 --- a/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.otakumap.global.apiPayload.exception.handler; - -import com.otakumap.global.apiPayload.code.BaseErrorCode; -import com.otakumap.global.apiPayload.exception.GeneralException; - -public class RouteHandler extends GeneralException { - public RouteHandler(BaseErrorCode errorCode) { - super(errorCode); - } -} From e62af7ba715bc2339ff498e8b69deaae0308e0d4 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 22 Jan 2025 12:45:22 +0900 Subject: [PATCH 158/516] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81(api=20=EC=88=98=EC=A0=95,=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=93=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventLikeController.java | 2 +- .../converter/EventLikeConverter.java | 10 ++++++++++ .../service/EventLikeCommandServiceImpl.java | 8 ++------ .../service/EventLikeQueryService.java | 1 - .../controller/ReviewSearchController.java | 9 +++++++++ .../reviews/converter/ReviewConverter.java | 9 +++++++++ .../domain/reviews/dto/ReviewResponseDTO.java | 20 +++++++++++++++++++ 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 44276cb5..89ede8b6 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -25,7 +25,7 @@ public class EventLikeController { private final EventLikeCommandService eventLikeCommandService; @Operation(summary = "이벤트 저장(찜하기)", description = "이벤트를 저장(찜)합니다.") - @GetMapping("/events/{eventId}/save") + @PostMapping("/events/{eventId}") @Parameters({ @Parameter(name = "eventId", description = "이벤트 ID") }) diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index 387776b2..e211285e 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -1,7 +1,9 @@ package com.otakumap.domain.event_like.converter; +import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.user.entity.User; import java.util.List; @@ -26,4 +28,12 @@ public static EventLikeResponseDTO.EventLikePreViewListDTO eventLikePreViewListD .lastId(lastId) .build(); } + + public static EventLike eventLike(User user, Event event) { + return EventLike.builder() + .event(event) + .user(user) + .isFavorite(true) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index 16628f79..85d34b21 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -2,7 +2,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; -import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.event_like.converter.EventLikeConverter; import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; @@ -27,11 +27,7 @@ public void addEventLike(User user, Long eventId) { Event event = eventRepository.findById(eventId) .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); eventLikeRepository.save( - EventLike.builder() - .event(event) - .user(user) - .isFavorite(true) - .build() + EventLikeConverter.eventLike(user, event) ); entityManager.flush(); entityManager.clear(); diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java index e1731f38..59630cda 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java @@ -1,7 +1,6 @@ package com.otakumap.domain.event_like.service; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; -import com.otakumap.domain.user.entity.User; public interface EventLikeQueryService { EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId, Integer type, Long lastId, int limit); diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index 369d79c9..d4842ef8 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -21,6 +21,15 @@ public class ReviewSearchController { private final ReviewQueryService reviewQueryService; + @GetMapping("/reviews/top7") + @Operation(summary = "조회수 Top7 여행 후기 목록 조회", description = "조회수 Top7 여행 후기 목록을 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + public ApiResponse getTop7ReviewList() { + return null; + } + @GetMapping("/reviews/search") @Operation(summary = "키워드로 여행 후기 검색", description = "키워드로 여행 후기를 검색해서 조회합니다.") @ApiResponses({ diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 1c05f4c3..77127c66 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -7,6 +7,15 @@ public class ReviewConverter { + public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7ReviewPreViewDTO(EventReview eventReview) { + return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() + .id(eventReview.getId()) + .title(eventReview.getTitle()) + .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .view(eventReview.getView()) + .build(); + } + public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPreviewDTO(EventReview eventReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(eventReview.getId()) diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 434e672c..ab50a926 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -7,9 +7,29 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; public class ReviewResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Top7ReviewPreViewListDTO { + List reviews; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class Top7ReviewPreViewDTO { + Long id; + String title; + ImageResponseDTO.ImageDTO reviewImage; + Long view; + } + @Builder @Getter @NoArgsConstructor From 725f48102e52084474b4e2fb628503b2b402031e Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:10:25 +0900 Subject: [PATCH 159/516] =?UTF-8?q?Chore:=20.gitIgnore=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c2065bc2..5688f668 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + +src/main/resources \ No newline at end of file From 1cd4f68fb2cff914e3cd959cb531f62ae9b0f386 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:18:35 +0900 Subject: [PATCH 160/516] =?UTF-8?q?Refactor:=20ErrorStatus=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/global/apiPayload/code/status/ErrorStatus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 422957ba..38f2c3f8 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -51,7 +51,7 @@ public enum ErrorStatus implements BaseErrorCode { ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), // 루트 좋아요 관련 에러 - ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."); + ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."), // 알림 관련 에러 INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."); From 37116867e600b296e1036e00f55590c6ae0716f7 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:51:49 +0900 Subject: [PATCH 161/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/route/entity/Route.java | 30 +++++++++++++++ .../otakumap/domain/route/enums/ItemType.java | 6 +++ .../route/repository/RouteRepository.java | 7 ++++ .../domain/route_item/entity/RouteItem.java | 33 +++++++++++++++++ .../controller/RouteLikeController.java | 34 +++++++++++++++++ .../converter/RouteLikeConverter.java | 16 ++++++++ .../domain/route_like/entity/RouteLike.java | 37 +++++++++++++++++++ .../repository/RouteLikeRepository.java | 10 +++++ .../service/RouteLikeCommandService.java | 7 ++++ .../service/RouteLikeCommandServiceImpl.java | 33 +++++++++++++++++ .../com/otakumap/domain/user/entity/User.java | 4 ++ .../exception/handler/RouteHandler.java | 10 +++++ 12 files changed, 227 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/route/entity/Route.java create mode 100644 src/main/java/com/otakumap/domain/route/enums/ItemType.java create mode 100644 src/main/java/com/otakumap/domain/route/repository/RouteRepository.java create mode 100644 src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java create mode 100644 src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java create mode 100644 src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java create mode 100644 src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java create mode 100644 src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java create mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java create mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java new file mode 100644 index 00000000..18278ebf --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.route.entity; + +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Route extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 50, nullable = false) + private String name; + + @OneToMany(mappedBy = "route", cascade = CascadeType.ALL) + private List routeLikes; + + @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) + private List routeItems; +} diff --git a/src/main/java/com/otakumap/domain/route/enums/ItemType.java b/src/main/java/com/otakumap/domain/route/enums/ItemType.java new file mode 100644 index 00000000..a49eb9a4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/enums/ItemType.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.route.enums; + +public enum ItemType { + PLACE, + EVENT +} diff --git a/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java b/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java new file mode 100644 index 00000000..bc75c002 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/repository/RouteRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.route.repository; + +import com.otakumap.domain.route.entity.Route; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RouteRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java new file mode 100644 index 00000000..da93c54f --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.route_item.entity; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route.enums.ItemType; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class RouteItem extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Integer itemOrder; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) + private ItemType itemType; + + @Column(nullable = false) + private Long itemId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "route_id") + private Route route; +} diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java new file mode 100644 index 00000000..7d87d8a6 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -0,0 +1,34 @@ +package com.otakumap.domain.route_like.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.route_like.service.RouteLikeCommandService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class RouteLikeController { + + private final RouteLikeCommandService routeLikeCommandService; + + @Operation(summary = "루트 저장", description = "루트를 저장합니다.") + @PostMapping("/routes/{routeId}") + @Parameters({ + @Parameter(name = "routeId", description = "루트 Id") + }) + public ApiResponse saveRouteLike(@PathVariable Long routeId, @CurrentUser User user) { + + routeLikeCommandService.saveRouteLike(user, routeId); + + return ApiResponse.onSuccess("루트가 성공적으로 저장되었습니다."); + } +} diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java new file mode 100644 index 00000000..c97b01fa --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.route_like.converter; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.domain.user.entity.User; + +public class RouteLikeConverter { + + public static RouteLike toRouteLike(User user, Route route) { + return RouteLike.builder() + .user(user) + .route(route) + .isFavorite(Boolean.TRUE) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java new file mode 100644 index 00000000..f7f53fdb --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -0,0 +1,37 @@ +package com.otakumap.domain.route_like.entity; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "route_like") +public class RouteLike extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "route_id") + private Route route; + + @Column(name = "is_favorite", nullable = false) + @ColumnDefault("false") + private Boolean isFavorite; +} diff --git a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java new file mode 100644 index 00000000..1bdff80d --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.route_like.repository; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RouteLikeRepository extends JpaRepository { + boolean existsByUserAndRoute(User user, Route route); +} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java new file mode 100644 index 00000000..90bacef1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.route_like.service; + +import com.otakumap.domain.user.entity.User; + +public interface RouteLikeCommandService { + void saveRouteLike(User user, Long routeId); +} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java new file mode 100644 index 00000000..7fe56873 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.route_like.service; + +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_like.converter.RouteLikeConverter; +import com.otakumap.domain.route_like.entity.RouteLike; +import com.otakumap.domain.route_like.repository.RouteLikeRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.RouteHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { + + private final RouteLikeRepository routeLikeRepository; + private final RouteRepository routeRepository; + private final UserRepository userRepository; + + @Override + public void saveRouteLike(User user, Long routeId) { + Route route = routeRepository.findById(routeId) + .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); + if(routeLikeRepository.existsByUserAndRoute(user, route)) { + throw new RouteHandler(ErrorStatus.ROUTE_LIKE_ALREADY_EXISTS); + } + RouteLike routeLike = RouteLikeConverter.toRouteLike(user, route); + routeLikeRepository.save(routeLike); + } +} diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index c950ef47..75645e22 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.place_like.entity.PlaceLike; +import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; import com.otakumap.domain.user.entity.enums.UserStatus; @@ -73,6 +74,9 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List placeLikes = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List routeLikes = new ArrayList<>(); + public void encodePassword(String password) { this.password = password; } diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java new file mode 100644 index 00000000..cfb7f2f0 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class RouteHandler extends GeneralException { + public RouteHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 2ecf441d7a5ae1e91c6aace62d19223eafb11de9 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:59:00 +0900 Subject: [PATCH 162/516] =?UTF-8?q?Fix:=20Test=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_like/PlaceLikeControllerTest.java | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java diff --git a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java b/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java deleted file mode 100644 index 145c4dc3..00000000 --- a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.otakumap.place_like; - -import com.otakumap.domain.place_like.controller.PlaceLikeController; -import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; -import com.otakumap.domain.place_like.service.PlaceLikeCommandService; -import com.otakumap.domain.place_like.service.PlaceLikeQueryService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import java.util.Arrays; -import java.util.List; - -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; - -@ExtendWith(MockitoExtension.class) -public class PlaceLikeControllerTest { - - @Mock - private PlaceLikeQueryService placeLikeQueryService; - - @Mock - private PlaceLikeCommandService placeLikeCommandService; - - @InjectMocks - private PlaceLikeController placeLikeController; - - private MockMvc mockMvc; - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(placeLikeController).build(); - } - - @Test - void testGetPlaceLikeList() throws Exception { - Long userId = 1L; - Long lastId = 0L; - int limit = 10; - - // PlaceLikePreViewDTO 객체 생성 - PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike1 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(1L, 1L, 101L, true); - PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike2 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(2L, 1L, 102L, false); - - // PlaceLikePreViewListDTO 객체 생성 - List placeLikes = Arrays.asList(placeLike1, placeLike2); - PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikeListDTO = new PlaceLikeResponseDTO.PlaceLikePreViewListDTO(placeLikes, true, 2L); - - when(placeLikeQueryService.getPlaceLikeList(userId, lastId, limit)).thenReturn(placeLikeListDTO); - - mockMvc.perform(get("/api/users/1/saved-places") - .param("lastId", "0") - .param("limit", "10") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.result.placeLikes[0].id").value(1)) - .andExpect(jsonPath("$.result.placeLikes[0].userId").value(1)) - .andExpect(jsonPath("$.result.placeLikes[0].placeId").value(101)) - .andExpect(jsonPath("$.result.placeLikes[0].isFavorite").value(true)); - - } - - @Test - void testDeletePlaceLike() throws Exception { - List placeIds = Arrays.asList(1L, 2L); - - // 삭제 동작 모의 - mockMvc.perform(delete("/api/saved-places") - .param("placeIds", "1,2") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - // "성공입니다."라는 메시지가 반환되면 이를 검증 - .andExpect(jsonPath("$.message").value("성공입니다.")); - } - -} From f0e747d0d57d03e29b1e73fb344e6fcd49be8315 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:01:35 +0900 Subject: [PATCH 163/516] =?UTF-8?q?Fix:=20PlaceLikeControllerTest=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/OtakumapApplicationTests.java | 7 +- .../place_like/PlaceLikeControllerTest.java | 86 ------------------- 2 files changed, 3 insertions(+), 90 deletions(-) delete mode 100644 src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java diff --git a/src/test/java/com/otakumap/OtakumapApplicationTests.java b/src/test/java/com/otakumap/OtakumapApplicationTests.java index c16a3767..52771c6a 100644 --- a/src/test/java/com/otakumap/OtakumapApplicationTests.java +++ b/src/test/java/com/otakumap/OtakumapApplicationTests.java @@ -1,13 +1,12 @@ package com.otakumap; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class OtakumapApplicationTests { - @Test - void contextLoads() { - } +// @Test +// void contextLoads() { +// } } diff --git a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java b/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java deleted file mode 100644 index 145c4dc3..00000000 --- a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.otakumap.place_like; - -import com.otakumap.domain.place_like.controller.PlaceLikeController; -import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; -import com.otakumap.domain.place_like.service.PlaceLikeCommandService; -import com.otakumap.domain.place_like.service.PlaceLikeQueryService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import java.util.Arrays; -import java.util.List; - -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; - -@ExtendWith(MockitoExtension.class) -public class PlaceLikeControllerTest { - - @Mock - private PlaceLikeQueryService placeLikeQueryService; - - @Mock - private PlaceLikeCommandService placeLikeCommandService; - - @InjectMocks - private PlaceLikeController placeLikeController; - - private MockMvc mockMvc; - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(placeLikeController).build(); - } - - @Test - void testGetPlaceLikeList() throws Exception { - Long userId = 1L; - Long lastId = 0L; - int limit = 10; - - // PlaceLikePreViewDTO 객체 생성 - PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike1 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(1L, 1L, 101L, true); - PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike2 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(2L, 1L, 102L, false); - - // PlaceLikePreViewListDTO 객체 생성 - List placeLikes = Arrays.asList(placeLike1, placeLike2); - PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikeListDTO = new PlaceLikeResponseDTO.PlaceLikePreViewListDTO(placeLikes, true, 2L); - - when(placeLikeQueryService.getPlaceLikeList(userId, lastId, limit)).thenReturn(placeLikeListDTO); - - mockMvc.perform(get("/api/users/1/saved-places") - .param("lastId", "0") - .param("limit", "10") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.result.placeLikes[0].id").value(1)) - .andExpect(jsonPath("$.result.placeLikes[0].userId").value(1)) - .andExpect(jsonPath("$.result.placeLikes[0].placeId").value(101)) - .andExpect(jsonPath("$.result.placeLikes[0].isFavorite").value(true)); - - } - - @Test - void testDeletePlaceLike() throws Exception { - List placeIds = Arrays.asList(1L, 2L); - - // 삭제 동작 모의 - mockMvc.perform(delete("/api/saved-places") - .param("placeIds", "1,2") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - // "성공입니다."라는 메시지가 반환되면 이를 검증 - .andExpect(jsonPath("$.message").value("성공입니다.")); - } - -} From cb16dd970b8112b8d64cdf7266faa8f27659930c Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 22 Jan 2025 17:55:45 +0900 Subject: [PATCH 164/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85,=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=BD=94=EB=93=9C=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 6 +++--- .../domain/auth/service/AuthCommandService.java | 3 ++- .../auth/service/AuthCommandServiceImpl.java | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index 0b357ea4..2d49ea2c 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -50,7 +50,7 @@ public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.Verify @Operation(summary = "이메일 코드 인증", description = "회원가입 시 이메일 코드 인증 기능입니다.") @PostMapping("/verify-code") public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyCodeDTO request) { - return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request, "signup"))); + return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request))); } @Operation(summary = "토큰 재발급", description = "accessToken이 만료 시 refreshToken을 통해 accessToken을 재발급합니다.") @@ -99,7 +99,7 @@ public ApiResponse findPassword(@RequestBody @Valid AuthRequestDTO.FindP @Operation(summary = "비밀번호 찾기 코드 인증", description = "비밀번호 찾기 시 이메일 코드 인증 기능입니다.") @PostMapping("/verify-password-code") - public ApiResponse verifyPasswordCode(@RequestBody @Valid AuthRequestDTO.VerifyCodeDTO request) { - return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyCode(request, "findPassword"))); + public ApiResponse verifyPasswordCode(@RequestBody @Valid AuthRequestDTO.VerifyResetCodeDTO request) { + return ApiResponse.onSuccess(UserConverter.toVerifyCodeResultDTO(authCommandService.verifyResetCode(request))); } } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java index 9d0a6e24..f9e5b643 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -11,8 +11,9 @@ public interface AuthCommandService { AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request); boolean checkId(AuthRequestDTO.CheckIdDTO request); void verifyEmail(AuthRequestDTO.VerifyEmailDTO request); - boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request, String requestType); + boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); JwtDTO reissueToken(String refreshToken); void logout(HttpServletRequest request); void findPassword(AuthRequestDTO.FindPasswordDTO request); + boolean verifyResetCode(AuthRequestDTO.VerifyResetCodeDTO request); } diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index eb4d62c8..c63f6247 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -74,8 +74,8 @@ public void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) { } @Override - public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request, String requestType) { - String authCode = (String) redisUtil.get("auth:" + request.getEmail() + ":" + requestType); + public boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request) { + String authCode = (String) redisUtil.get("auth:" + request.getEmail() + ":" + "signup"); if (authCode == null) { throw new AuthHandler(ErrorStatus.EMAIL_CODE_EXPIRED); } @@ -122,4 +122,17 @@ public void findPassword(AuthRequestDTO.FindPasswordDTO request) { throw new AuthHandler(ErrorStatus.EMAIL_SEND_FAILED); } } + + @Override + public boolean verifyResetCode(AuthRequestDTO.VerifyResetCodeDTO request) { + User user = userRepository.findByUserId(request.getUserId()).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)); + String authCode = (String) redisUtil.get("auth:" + user.getEmail() + ":" + "findPassword"); + if (authCode == null) { + throw new AuthHandler(ErrorStatus.EMAIL_CODE_EXPIRED); + } + if (!authCode.equals(request.getCode())) { + throw new AuthHandler(ErrorStatus.CODE_NOT_EQUAL); + } + return true; + } } From 92a6d51298e8872b61431cc976831528decb3431 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 22 Jan 2025 17:57:31 +0900 Subject: [PATCH 165/516] =?UTF-8?q?Feat:=20=EB=B9=84=EB=B9=8C=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 7 +++++++ .../domain/user/dto/UserRequestDTO.java | 21 ++++++++++++++++++- .../user/service/UserCommandService.java | 1 + .../user/service/UserCommandServiceImpl.java | 16 ++++++++++++++ .../global/config/SecurityConfig.java | 3 ++- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index a654f9cb..ea78d168 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -70,4 +70,11 @@ public ApiResponse getMyReviews( Page reviews = userQueryService.getMyReviews(user, page, sort); return ApiResponse.onSuccess(UserConverter.reviewListDTO(reviews)); } + + @Operation(summary = "비밀번호 변경", description = "비밀번호를 변경합니다.") + @PostMapping("/reset-password") + public ApiResponse resetPassword(@RequestBody @Valid UserRequestDTO.ResetPasswordDTO request) { + userCommandService.resetPassword(request); + return ApiResponse.onSuccess("비밀번호가 성공적으로 변경되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 5ceae25a..fa62e827 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.user.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.Getter; @@ -32,4 +33,22 @@ public static class NotificationSettingsRequestDTO { @NotNull private boolean isEnabled; } -} + + @Getter + public static class ResetPasswordDTO { + @NotBlank(message = "아이디 입력은 필수 입니다.") + String userId; + + @NotBlank(message = "비밀번호 입력은 필수입니다.") + @Schema(description = "password", example = "otakumap1234!") + @Pattern( + regexp = "^(?!.*(\\d)\\1{2})(?=(.*[A-Za-z]){1})(?=(.*\\d){1})(?!.*\\s).{10,}$|^(?!.*(\\d)\\1{2})(?=(.*[A-Za-z]){1})(?=(.*[^A-Za-z0-9]){1})(?!.*\\s).{10,}$|^(?!.*(\\d)\\1{2})(?=(.*\\d){1})(?=(.*[^A-Za-z0-9]){1})(?!.*\\s).{10,}$", + message = "비밀번호는 영문, 숫자, 특수문자 중 2종류 이상을 조합하여 10자리 이상이어야 하며, 동일한 숫자 3개 이상을 연속해서 사용할 수 없습니다." + ) + String password; + + @NotBlank(message = "비밀번호 재확인 입력은 필수입니다.") + @Schema(description = "password", example = "otakumap1234!") + String passwordCheck; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java index 4c465558..db0b006e 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -7,4 +7,5 @@ public interface UserCommandService { void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request); void reportEvent(UserRequestDTO.UserReportRequestDTO request); void updateNotificationSettings(User user, UserRequestDTO.NotificationSettingsRequestDTO request); + void resetPassword(UserRequestDTO.ResetPasswordDTO request); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index cd0089da..2e51d177 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -1,13 +1,16 @@ package com.otakumap.domain.user.service; +import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; import com.otakumap.global.util.EmailUtil; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @@ -15,6 +18,7 @@ public class UserCommandServiceImpl implements UserCommandService { private final UserRepository userRepository; private final EmailUtil emailUtil; + private final PasswordEncoder passwordEncoder; @Override @Transactional @@ -50,4 +54,16 @@ public void updateNotificationSettings(User user, UserRequestDTO.NotificationSet user.setNotification(request.getNotificationType(), request.isEnabled()); userRepository.save(user); } + + @Override + public void resetPassword(UserRequestDTO.ResetPasswordDTO request) { + User user = userRepository.findByUserId(request.getUserId()).orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + + if(!request.getPassword().equals(request.getPasswordCheck())) { + throw new AuthHandler(ErrorStatus.PASSWORD_NOT_EQUAL); + } + + user.encodePassword(passwordEncoder.encode(request.getPassword())); + userRepository.save(user); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 0230982b..a45ce3e8 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -32,7 +32,8 @@ public class SecurityConfig { "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**", - "/api/auth/**" + "/api/auth/**", + "/api/users/reset-password/**" }; @Bean From 9b9d0ba5d1b877408fb400f436bcdbb13e4775f7 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 22 Jan 2025 17:58:13 +0900 Subject: [PATCH 166/516] =?UTF-8?q?Feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B0=BE=EA=B8=B0=20=EC=BD=94=EB=93=9C=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20DTO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/auth/dto/AuthRequestDTO.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index af5b7809..35f90d5e 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -81,4 +81,13 @@ public static class SocialLoginDTO { @NotNull String code; } + + @Getter + public static class VerifyResetCodeDTO { + @NotBlank(message = "인증 코드 입력은 필수입니다.") + String code; + + @NotBlank(message = "아이디 입력은 필수입니다.") + String userId; + } } From cd7a04d858aeb17e9e4065e998da4b44d76865bd Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 22 Jan 2025 23:55:17 +0900 Subject: [PATCH 167/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EC=A7=81=EC=97=90=EC=84=9C=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EC=A4=91=EB=B3=B5=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/auth/controller/AuthController.java | 6 ------ .../java/com/otakumap/domain/auth/dto/AuthRequestDTO.java | 4 ---- .../otakumap/domain/auth/service/AuthCommandService.java | 1 - .../domain/auth/service/AuthCommandServiceImpl.java | 5 ----- 4 files changed, 16 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index 1072b2ef..3beb99b2 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -32,12 +32,6 @@ public ApiResponse login(@RequestBody @Valid Aut return ApiResponse.onSuccess(authCommandService.login(request)); } - @Operation(summary = "닉네임 중복 확인", description = "닉네임 중복 확인 기능입니다.") - @PostMapping("/check-nickname") - public ApiResponse checkNickname(@RequestBody @Valid AuthRequestDTO.CheckNicknameDTO request) { - return ApiResponse.onSuccess(UserConverter.toCheckNicknameResultDTO(authCommandService.checkNickname(request))); - } - @Operation(summary = "아이디 중복 확인", description = "아이디 중복 확인 기능입니다.") @PostMapping("/check-id") public ApiResponse checkId(@RequestBody @Valid AuthRequestDTO.CheckIdDTO request) { diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index 90be4ff4..68904819 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -13,10 +13,6 @@ public static class SignupDTO { @Schema(description = "name", example = "오타쿠맵") String name; - @NotBlank(message = "닉네임 입력은 필수입니다.") - @Schema(description = "nickname", example = "오타쿠") - String nickname; - @NotBlank(message = "아이디 입력은 필수입니다.") @Schema(description = "userId", example = "otakumap1234") String userId; diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java index 181eea5d..1c1a7e73 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -10,7 +10,6 @@ public interface AuthCommandService { User signup(AuthRequestDTO.SignupDTO request); AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request); - boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request); boolean checkId(AuthRequestDTO.CheckIdDTO request); void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index 5ca06fe3..cdf198d7 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -57,11 +57,6 @@ public AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request) { return UserConverter.toLoginResultDTO(user, accessToken, refreshToken); } - @Override - public boolean checkNickname(AuthRequestDTO.CheckNicknameDTO request) { - return userRepository.existsByNickname(request.getNickname()); - } - @Override public boolean checkId(AuthRequestDTO.CheckIdDTO request) { return userRepository.existsByUserId(request.getUserId()); From cc5d6e058bdd408de0ea3225434b29c780068a9c Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 22 Jan 2025 23:59:14 +0900 Subject: [PATCH 168/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EC=A7=81=EC=97=90=EC=84=9C=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EC=9E=85=EB=A0=A5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/user/converter/UserConverter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index b3cd33f5..1ce822d3 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -18,7 +18,6 @@ public class UserConverter { public static User toUser(AuthRequestDTO.SignupDTO request) { return User.builder() .name(request.getName()) - .nickname(request.getNickname()) .userId(request.getUserId()) .email(request.getEmail()) .password(request.getPassword()) From ecc6b03563c3fb152a7da62f302b1252e5441808 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 23 Jan 2025 00:01:50 +0900 Subject: [PATCH 169/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=95=84=EC=9D=B4=EB=94=94,=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/auth/dto/AuthRequestDTO.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index 68904819..b0179175 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -15,6 +15,7 @@ public static class SignupDTO { @NotBlank(message = "아이디 입력은 필수입니다.") @Schema(description = "userId", example = "otakumap1234") + @Pattern(regexp = "^(?=.*[a-zA-Z])[a-zA-Z0-9]{6,}$", message = "아이디는 최소 6자 이상이며, 영문 또는 영문과 숫자의 조합이어야 합니다.") String userId; @NotBlank(message = "이메일 입력은 필수입니다.") @@ -24,8 +25,10 @@ public static class SignupDTO { @NotBlank(message = "비밀번호 입력은 필수입니다.") @Schema(description = "password", example = "otakumap1234!") - @Pattern(regexp = "^(?:(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,})|(?:(?=.*[a-zA-Z])(?=.*\\d).{10,})|(?:(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{10,})|(?:(?=.*\\d)(?=.*[!@#$%^&*]).{10,})$", - message = "영문, 숫자, 특수문자 중 2종류 이상을 조합하여 최소 10자리 이상이거나, 영문, 숫자, 특수문자 모두를 포함하여 최소 8자리 이상 입력해야 합니다.") + @Pattern( + regexp = "^(?!.*(\\d)\\1{2})(?=(.*[A-Za-z]){1})(?=(.*\\d){1})(?!.*\\s).{10,}$|^(?!.*(\\d)\\1{2})(?=(.*[A-Za-z]){1})(?=(.*[^A-Za-z0-9]){1})(?!.*\\s).{10,}$|^(?!.*(\\d)\\1{2})(?=(.*\\d){1})(?=(.*[^A-Za-z0-9]){1})(?!.*\\s).{10,}$", + message = "비밀번호는 영문, 숫자, 특수문자 중 2종류 이상을 조합하여 10자리 이상이어야 하며, 동일한 숫자 3개 이상을 연속해서 사용할 수 없습니다." + ) String password; @NotBlank(message = "비밀번호 재확인 입력은 필수 입니다.") From 3d8166c5b341caeceafe5beab0d459fa4a156366 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 23 Jan 2025 00:07:02 +0900 Subject: [PATCH 170/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=ED=99=95=EC=9D=B8=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 6 ++++++ .../com/otakumap/domain/auth/dto/AuthRequestDTO.java | 10 +++++----- .../com/otakumap/domain/auth/dto/AuthResponseDTO.java | 8 ++++++++ .../domain/auth/service/AuthCommandService.java | 1 + .../domain/auth/service/AuthCommandServiceImpl.java | 5 +++++ .../otakumap/domain/user/converter/UserConverter.java | 8 ++++---- 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java index 3beb99b2..b1df27ed 100644 --- a/src/main/java/com/otakumap/domain/auth/controller/AuthController.java +++ b/src/main/java/com/otakumap/domain/auth/controller/AuthController.java @@ -38,6 +38,12 @@ public ApiResponse checkId(@RequestBody @Valid return ApiResponse.onSuccess(UserConverter.toCheckIdResultDTO(authCommandService.checkId(request))); } + @Operation(summary = "이메일 중복 확인", description = "이메일 중복 확인 기능입니다.") + @PostMapping("/check-email") + public ApiResponse checkEmail(@RequestBody @Valid AuthRequestDTO.CheckEmailDTO request) { + return ApiResponse.onSuccess(UserConverter.toCheckEmailResultDTO(authCommandService.checkEmail(request))); + } + @Operation(summary = "이메일 인증 메일 전송", description = "이메일 인증을 위한 메일 전송 기능입니다.") @PostMapping("/verify-email") public ApiResponse verifyEmail(@RequestBody @Valid AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index b0179175..b16f5c27 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -45,15 +45,15 @@ public static class LoginDTO { } @Getter - public static class CheckNicknameDTO { + public static class CheckIdDTO { @NotNull - String nickname; + String userId; } @Getter - public static class CheckIdDTO { - @NotNull - String userId; + public static class CheckEmailDTO { + @NotBlank(message = "이메일 입력은 필수입니다.") + String email; } @Getter diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java index b1935af0..57530ab0 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthResponseDTO.java @@ -44,6 +44,14 @@ public static class CheckIdResultDTO { boolean isDuplicated; } + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CheckEmailResultDTO { + boolean isDuplicated; + } + @Getter @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java index 1c1a7e73..306c08a5 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandService.java @@ -11,6 +11,7 @@ public interface AuthCommandService { User signup(AuthRequestDTO.SignupDTO request); AuthResponseDTO.LoginResultDTO login(AuthRequestDTO.LoginDTO request); boolean checkId(AuthRequestDTO.CheckIdDTO request); + boolean checkEmail(AuthRequestDTO.CheckEmailDTO request); void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException; boolean verifyCode(AuthRequestDTO.VerifyCodeDTO request); JwtDTO reissueToken(String refreshToken); diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index cdf198d7..730af5c0 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -62,6 +62,11 @@ public boolean checkId(AuthRequestDTO.CheckIdDTO request) { return userRepository.existsByUserId(request.getUserId()); } + @Override + public boolean checkEmail(AuthRequestDTO.CheckEmailDTO request) { + return userRepository.existsByEmail(request.getEmail()); + } + @Override public void verifyEmail(AuthRequestDTO.VerifyEmailDTO request) throws MessagingException { try { diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 1ce822d3..44749c64 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -44,14 +44,14 @@ public static AuthResponseDTO.LoginResultDTO toLoginResultDTO(User user, String } - public static AuthResponseDTO.CheckNicknameResultDTO toCheckNicknameResultDTO(boolean isDuplicated) { - return AuthResponseDTO.CheckNicknameResultDTO.builder() + public static AuthResponseDTO.CheckIdResultDTO toCheckIdResultDTO(boolean isDuplicated) { + return AuthResponseDTO.CheckIdResultDTO.builder() .isDuplicated(isDuplicated) .build(); } - public static AuthResponseDTO.CheckIdResultDTO toCheckIdResultDTO(boolean isDuplicated) { - return AuthResponseDTO.CheckIdResultDTO.builder() + public static AuthResponseDTO.CheckEmailResultDTO toCheckEmailResultDTO(boolean isDuplicated) { + return AuthResponseDTO.CheckEmailResultDTO.builder() .isDuplicated(isDuplicated) .build(); } From 78e918cfbb2dea034af5a20db67e678adfdfd7bb Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 23 Jan 2025 00:11:41 +0900 Subject: [PATCH 171/516] =?UTF-8?q?Feat:=20auth=20=EA=B4=80=EB=A0=A8=20DTO?= =?UTF-8?q?=EC=97=90=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/dto/AuthRequestDTO.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java index b16f5c27..4f1790db 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java +++ b/src/main/java/com/otakumap/domain/auth/dto/AuthRequestDTO.java @@ -31,22 +31,23 @@ public static class SignupDTO { ) String password; - @NotBlank(message = "비밀번호 재확인 입력은 필수 입니다.") + @NotBlank(message = "비밀번호 재확인 입력은 필수입니다.") @Schema(description = "passwordCheck", example = "otakumap1234!") String passwordCheck; } @Getter public static class LoginDTO { - @NotNull + @NotBlank(message = "아이디 입력은 필수입니다.") String userId; - @NotNull + + @NotBlank(message = "비밀번호 입력은 필수입니다.") String password; } @Getter public static class CheckIdDTO { - @NotNull + @NotBlank(message = "아이디 입력은 필수입니다.") String userId; } @@ -58,21 +59,22 @@ public static class CheckEmailDTO { @Getter public static class VerifyEmailDTO { - @NotNull + @NotBlank(message = "이메일 입력은 필수입니다.") String email; } @Getter public static class VerifyCodeDTO { - @NotNull + @NotBlank(message = "인증 코드 입력은 필수입니다.") String code; - @NotNull + + @NotBlank(message = "이메일 입력은 필수입니다.") String email; } @Getter public static class SocialLoginDTO { - @NotNull + @NotBlank(message = "인가 코드 입력은 필수입니다.") String code; } } From 5cb50039a9d70b8a137144891304a2a6ac6cf774 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 23 Jan 2025 21:30:33 +0900 Subject: [PATCH 172/516] =?UTF-8?q?Feat:=20=EC=95=84=EC=9D=B4=EB=94=94,=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=A4=91=EB=B3=B5=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20GET=20=EC=9A=94=EC=B2=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthQueryService.java | 7 +++++ .../auth/service/AuthQueryServiceImpl.java | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java create mode 100644 src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java b/src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java new file mode 100644 index 00000000..8ec4e507 --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/AuthQueryService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.auth.service; + +public interface AuthQueryService { + boolean checkId(String userId); + boolean checkEmail(String email); + String findId(String name, String email); +} diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java new file mode 100644 index 00000000..4790c2bc --- /dev/null +++ b/src/main/java/com/otakumap/domain/auth/service/AuthQueryServiceImpl.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.auth.service; + +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthQueryServiceImpl implements AuthQueryService { + private final UserRepository userRepository; + + @Override + public boolean checkId(String userId) { + return userRepository.existsByUserId(userId); + } + + @Override + public boolean checkEmail(String email) { + return userRepository.existsByEmail(email); + } + + @Override + public String findId(String name, String email) { + return userRepository.findByNameAndEmail(name, email).orElseThrow(() -> new AuthHandler(ErrorStatus.USER_NOT_FOUND)).getUserId(); + } +} From 52b86887a22d6f9121a6aa382822c38c83a39e94 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 23 Jan 2025 21:41:25 +0900 Subject: [PATCH 173/516] =?UTF-8?q?Refactor:=20=EC=A0=80=EC=9E=A5=ED=95=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EC=97=90=20@CurrentUser=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventLikeController.java | 16 +++++++--------- .../event_like/converter/EventLikeConverter.java | 3 ++- .../service/EventLikeQueryService.java | 3 ++- .../service/EventLikeQueryServiceImpl.java | 7 +++---- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 89ede8b6..388c63c1 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -17,7 +17,7 @@ import java.util.List; @RestController -@RequestMapping("/api") +@RequestMapping("/api/events") @RequiredArgsConstructor @Validated public class EventLikeController { @@ -25,7 +25,7 @@ public class EventLikeController { private final EventLikeCommandService eventLikeCommandService; @Operation(summary = "이벤트 저장(찜하기)", description = "이벤트를 저장(찜)합니다.") - @PostMapping("/events/{eventId}") + @PostMapping("/{eventId}") @Parameters({ @Parameter(name = "eventId", description = "이벤트 ID") }) @@ -34,23 +34,21 @@ public ApiResponse postPlaceLike(@CurrentUser User user, @PathVariable L return ApiResponse.onSuccess("이벤트가 성공적으로 저장되었습니다."); } - // 로그인 시 엔드포인트 변경 예정 @Operation(summary = "저장된 이벤트 목록 조회", description = "저장된 이벤트 목록을 불러옵니다.") - @GetMapping( "/users/{userId}/saved-events") + @GetMapping( "/saved") @Parameters({ - @Parameter(name = "userId", description = "사용자 ID"), @Parameter(name = "type", description = "이벤트 타입 -> 1: 팝업 스토어, 2: 전시회, 3: 콜라보 카페"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") }) - public ApiResponse getEventLikeList(@PathVariable Long userId, @RequestParam(required = false) Integer type, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { - return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(userId, type, lastId, limit)); + public ApiResponse getEventLikeList(@CurrentUser User user, @RequestParam(required = false) Integer type, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(user, type, lastId, limit)); } @Operation(summary = "저장된 이벤트 삭제", description = "저장된 이벤트를 삭제합니다.") - @DeleteMapping("/saved-events") + @DeleteMapping("/saved") @Parameters({ - @Parameter(name = "eventIds", description = "저장된 이벤트 id List"), + @Parameter(name = "eventIds", description = "저장된 이벤트 ID List"), }) public ApiResponse deleteEventLike(@RequestParam(required = false) @ExistEventLike List eventIds) { eventLikeCommandService.deleteEventLike(eventIds); diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index e211285e..eeb20a4e 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -3,6 +3,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.image.dto.ImageResponseDTO; import com.otakumap.domain.user.entity.User; import java.util.List; @@ -13,7 +14,7 @@ public static EventLikeResponseDTO.EventLikePreViewDTO eventLikePreViewDTO(Event .id(eventLike.getId()) .eventId(eventLike.getEvent().getId()) .name(eventLike.getEvent().getName()) -// .thumbnail(eventLike.getEvent().getThumbnail()) + .thumbnail(eventLike.getEvent().getThumbnailImage().getFileUrl()) .startDate(eventLike.getEvent().getStartDate()) .endDate(eventLike.getEvent().getEndDate()) .isFavorite(eventLike.getIsFavorite()) diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java index 59630cda..fe3b4873 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java @@ -1,8 +1,9 @@ package com.otakumap.domain.event_like.service; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.user.entity.User; public interface EventLikeQueryService { - EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId, Integer type, Long lastId, int limit); + EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Long lastId, int limit); boolean isEventLikeExist(Long id); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java index 81d2e894..62757f9e 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java @@ -27,10 +27,9 @@ public class EventLikeQueryServiceImpl implements EventLikeQueryService { private final UserRepository userRepository; @Override - public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId, Integer type, Long lastId, int limit) { + public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Long lastId, int limit) { List result; Pageable pageable = PageRequest.of(0, limit + 1); - User user = userRepository.findById(userId).orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); EventType eventType = (type == null || type == 0) ? null : EventType.values()[type - 1]; if (lastId.equals(0L)) { @@ -43,11 +42,11 @@ public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(Long userId ? eventLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventLike.getCreatedAt(), pageable).getContent() : eventLikeRepository.findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventType, eventLike.getCreatedAt(), pageable).getContent(); } - return createEventLikePreviewListDTO(user, result, limit); + return createEventLikePreviewListDTO(result, limit); } - private EventLikeResponseDTO.EventLikePreViewListDTO createEventLikePreviewListDTO(User user, List eventLikes, int limit) { + private EventLikeResponseDTO.EventLikePreViewListDTO createEventLikePreviewListDTO(List eventLikes, int limit) { boolean hasNext = eventLikes.size() > limit; Long lastId = null; if (hasNext) { From 6ce35598b8de8f580aecdfe68a6cee71313022e0 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 23 Jan 2025 21:49:39 +0900 Subject: [PATCH 174/516] =?UTF-8?q?Refactor:=20API=20Endpoint=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/controller/EventLikeController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 388c63c1..9e774f4c 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -35,7 +35,7 @@ public ApiResponse postPlaceLike(@CurrentUser User user, @PathVariable L } @Operation(summary = "저장된 이벤트 목록 조회", description = "저장된 이벤트 목록을 불러옵니다.") - @GetMapping( "/saved") + @GetMapping( "/liked") @Parameters({ @Parameter(name = "type", description = "이벤트 타입 -> 1: 팝업 스토어, 2: 전시회, 3: 콜라보 카페"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @@ -46,7 +46,7 @@ public ApiResponse getEventLikeLis } @Operation(summary = "저장된 이벤트 삭제", description = "저장된 이벤트를 삭제합니다.") - @DeleteMapping("/saved") + @DeleteMapping("/liked") @Parameters({ @Parameter(name = "eventIds", description = "저장된 이벤트 ID List"), }) From bfd93368d4d908f1d39e136e3ea7a901cee6344c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 24 Jan 2025 18:01:45 +0900 Subject: [PATCH 175/516] Chore: remove application.yml from local --- src/main/resources/application.yml | 68 ------------------------------ 1 file changed, 68 deletions(-) delete mode 100644 src/main/resources/application.yml diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 596dc4d0..00000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,68 +0,0 @@ -spring: - datasource: - url: ${DB_URL} - username: ${DB_USER} - password: ${DB_PASSWORD} - driver-class-name: com.mysql.cj.jdbc.Driver - sql: - init: - mode: never - jpa: - properties: - hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect - show_sql: true - format_sql: true - use_sql_comments: true - hbm2ddl: - auto: update - default_batch_fetch_size: 1000 - mail: - host: ${MAIL_HOST} - port: ${MAIL_PORT} - username: ${MAIL_USERNAME} - password: ${MAIL_PASSWORD} - properties: - mail: - smtp: - auth: true - starttls: - enable: true - required: true - connectiontimeout: 5000 - timeout: 5000 - writetimeout: 5000 - auth-code-expiration-millis: 1800000 # 30 * 60 * 1000 == 30? - - data: - redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} - repositories: - enabled: false - - jwt: - secret: ${JWT_SECRET} - token: - access-expiration-time: 3600000 #1시간 - refresh-expiration-time: 604800000 #7일 - -social: - kakao: - client-id: ${KAKAO_CLIENT_ID} - client-secret: ${KAKAO_CLIENT_SECRET} - redirect-uri: ${KAKAO_REDIRECT_URI} - token-uri: ${KAKAO_TOKEN_URI} - user-info-uri: ${KAKAO_USERINFO_URI} - google: - client-id: ${GOOGLE_CLIENT_ID} - client-secret: ${GOOGLE_CLIENT_SECRET} - redirect-uri: ${GOOGLE_REDIRECT_URI} - token-uri: ${GOOGLE_TOKEN_URI} - user-info-uri: ${GOOGLE_USERINFO_URI} - naver: - client-id: ${NAVER_CLIENT_ID} - client-secret: ${NAVER_CLIENT_SECRET} - redirect-uri: ${NAVER_REDIRECT_URI} - token-uri: ${NAVER_TOKEN_URI} - user-info-uri: ${NAVER_USERINFO_URI} \ No newline at end of file From 0b6b319c83de5b28963ae566b00fecf292948e09 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 24 Jan 2025 19:23:27 +0900 Subject: [PATCH 176/516] =?UTF-8?q?Feat:=20=EC=95=8C=EB=A6=BC=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=8C=20=EC=B2=98=EB=A6=AC=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 14 ++++++-- .../notification/entity/Notification.java | 5 +++ .../service/NotificationCommandService.java | 5 +++ .../NotificationCommandServiceImpl.java | 33 +++++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 5 ++- .../handler/NotificationHandler.java | 10 ++++++ 6 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java create mode 100644 src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/NotificationHandler.java diff --git a/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java b/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java index c811bbaf..b3b9f0aa 100644 --- a/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java +++ b/src/main/java/com/otakumap/domain/notification/controller/NotificationController.java @@ -5,13 +5,12 @@ import com.otakumap.domain.notification.dto.NotificationResponseDTO; import com.otakumap.domain.notification.entity.Notification; import com.otakumap.domain.notification.service.NotificationQueryService; +import com.otakumap.domain.notification.service.NotificationCommandService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -20,6 +19,7 @@ @RequestMapping("/api/notifications") public class NotificationController { private final NotificationQueryService notificationQueryService; + private final NotificationCommandService notificationCommandService; @GetMapping @Operation(summary = "알림 목록 조회 API", description = "읽지 않은 알림 목록을 조회합니다.") @@ -27,4 +27,12 @@ public ApiResponse getUnreadNotific List notifications = notificationQueryService.getUnreadNotifications(user.getId()); return ApiResponse.onSuccess(NotificationConverter.notificationListDTO(notifications)); } + + @PatchMapping("/{notificationId}/read") + @Operation(summary = "알림 읽음 처리 API", description = "알림을 읽음 처리합니다.") + public ApiResponse readNotification( + @CurrentUser User user, @PathVariable Long notificationId) { + notificationCommandService.markAsRead(user.getId(), notificationId); + return ApiResponse.onSuccess("알림이 읽음처리 되었습니다."); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/notification/entity/Notification.java b/src/main/java/com/otakumap/domain/notification/entity/Notification.java index 83d4bcfa..fe8d4ae3 100644 --- a/src/main/java/com/otakumap/domain/notification/entity/Notification.java +++ b/src/main/java/com/otakumap/domain/notification/entity/Notification.java @@ -36,4 +36,9 @@ public class Notification extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; + + public void setIsRead(boolean isRead) { + this.isRead = isRead; + if (isRead) { this.readAt = LocalDateTime.now(); } + } } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java new file mode 100644 index 00000000..20c7f45c --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.notification.service; + +public interface NotificationCommandService { + void markAsRead(Long userId, Long notificationId); +} diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java new file mode 100644 index 00000000..bf95cbdb --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.notification.service; + +import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.notification.repository.NotificationRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.NotificationHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NotificationCommandServiceImpl implements NotificationCommandService{ + private final NotificationRepository notificationRepository; + + @Override + @Transactional + public void markAsRead(Long userId, Long notificationId) { + Notification notification = notificationRepository.findById(notificationId) + .orElseThrow(() -> new NotificationHandler(ErrorStatus.NOTIFICATION_NOT_FOUND)); + + if (!notification.getUser().getId().equals(userId)) { + throw new NotificationHandler(ErrorStatus.NOTIFICATION_ACCESS_DENIED); + } + + if (notification.isRead()) { + throw new NotificationHandler(ErrorStatus.NOTIFICATION_ALREADY_READ); + } + + notification.setIsRead(true); + notificationRepository.save(notification); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 90f80f29..4d975c7f 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -47,7 +47,10 @@ public enum ErrorStatus implements BaseErrorCode { REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), // 알림 관련 에러 - INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."); + INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."), + NOTIFICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "NOTIFICATION4002", "존재하지 않는 알림입니다."), + NOTIFICATION_ALREADY_READ(HttpStatus.BAD_REQUEST, "NOTIFICATION4003", "이미 읽은 알림입니다."), + NOTIFICATION_ACCESS_DENIED(HttpStatus.BAD_REQUEST, "NOTIFICATION4004", "알림에 접근할 수 없습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/NotificationHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/NotificationHandler.java new file mode 100644 index 00000000..a68d9f72 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/NotificationHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class NotificationHandler extends GeneralException { + public NotificationHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From e582e60b9c6208bb42b90c0e6870e1caa5a241dd Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 24 Jan 2025 23:11:57 +0900 Subject: [PATCH 177/516] Refactor: correct HTTP status --- .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 4d975c7f..70274aa1 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -48,9 +48,9 @@ public enum ErrorStatus implements BaseErrorCode { // 알림 관련 에러 INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."), - NOTIFICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "NOTIFICATION4002", "존재하지 않는 알림입니다."), + NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION4002", "존재하지 않는 알림입니다."), NOTIFICATION_ALREADY_READ(HttpStatus.BAD_REQUEST, "NOTIFICATION4003", "이미 읽은 알림입니다."), - NOTIFICATION_ACCESS_DENIED(HttpStatus.BAD_REQUEST, "NOTIFICATION4004", "알림에 접근할 수 없습니다."); + NOTIFICATION_ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "NOTIFICATION4004", "알림에 접근할 수 없습니다."); private final HttpStatus httpStatus; private final String code; From d91da74998742040d2db765971e52e66304469a8 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 24 Jan 2025 23:52:10 +0900 Subject: [PATCH 178/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteLikeController.java | 21 ++++++++-- .../service/RouteLikeCommandService.java | 5 +++ .../service/RouteLikeCommandServiceImpl.java | 14 ++++++- .../service/RouteLikeQueryService.java | 5 +++ .../service/RouteLikeQueryServiceImpl.java | 18 +++++++++ .../apiPayload/code/status/ErrorStatus.java | 1 + .../validation/annotation/ExistRouteLike.java | 18 +++++++++ .../validator/RouteLikeExistValidator.java | 39 +++++++++++++++++++ 8 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java create mode 100644 src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistRouteLike.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/RouteLikeExistValidator.java diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 7d87d8a6..74e84b75 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -4,18 +4,20 @@ import com.otakumap.domain.route_like.service.RouteLikeCommandService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ExistRouteLike; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequestMapping("/api") @RequiredArgsConstructor +@Validated public class RouteLikeController { private final RouteLikeCommandService routeLikeCommandService; @@ -31,4 +33,15 @@ public ApiResponse saveRouteLike(@PathVariable Long routeId, @CurrentUse return ApiResponse.onSuccess("루트가 성공적으로 저장되었습니다."); } + + @Operation(summary = "저장된 루트 삭제", description = "저장된 루트를 삭제합니다.") + @DeleteMapping("/routes/liked") + @Parameters({ + @Parameter(name = "routeIds", description = "저장된 루트 id List"), + }) + public ApiResponse deleteSavedRoute(@RequestParam(required = false) @ExistRouteLike List routeIds) { + routeLikeCommandService.deleteRouteLike(routeIds); + return ApiResponse.onSuccess("저장된 루트가 성공적으로 삭제되었습니다."); + } + } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java index 90bacef1..235b5f6a 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -1,7 +1,12 @@ package com.otakumap.domain.route_like.service; import com.otakumap.domain.user.entity.User; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service public interface RouteLikeCommandService { void saveRouteLike(User user, Long routeId); + void deleteRouteLike(List routeIds); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 7fe56873..ef792974 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -9,16 +9,20 @@ import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.RouteHandler; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { private final RouteLikeRepository routeLikeRepository; private final RouteRepository routeRepository; - private final UserRepository userRepository; + private final EntityManager entityManager; @Override public void saveRouteLike(User user, Long routeId) { @@ -30,4 +34,12 @@ public void saveRouteLike(User user, Long routeId) { RouteLike routeLike = RouteLikeConverter.toRouteLike(user, route); routeLikeRepository.save(routeLike); } + + @Transactional + @Override + public void deleteRouteLike(List routeIds) { + routeLikeRepository.deleteAllByIdInBatch(routeIds); + entityManager.flush(); + entityManager.clear(); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java new file mode 100644 index 00000000..d076aee5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.route_like.service; + +public interface RouteLikeQueryService { + boolean isRouteLikeExist(Long id); +} diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java new file mode 100644 index 00000000..1ab647e5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java @@ -0,0 +1,18 @@ +package com.otakumap.domain.route_like.service; + +import com.otakumap.domain.route_like.repository.RouteLikeRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class RouteLikeQueryServiceImpl implements RouteLikeQueryService { + private final RouteLikeRepository routeLikeRepository; + + @Override + public boolean isRouteLikeExist(Long id) { + return routeLikeRepository.existsById(id); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 2cc11f6e..5ac7239e 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -52,6 +52,7 @@ public enum ErrorStatus implements BaseErrorCode { // 루트 좋아요 관련 에러 ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."), + ROUTE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "ROUTE4003", "저장되지 않은 루트입니다."), // 알림 관련 에러 INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."); diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistRouteLike.java b/src/main/java/com/otakumap/global/validation/annotation/ExistRouteLike.java new file mode 100644 index 00000000..c94ac195 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistRouteLike.java @@ -0,0 +1,18 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.RouteLikeExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = RouteLikeExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistRouteLike { + String message() default "유효하지 않은 루트 ID가 포함되어 있습니다."; + Class[] groups() default {}; + Class[] payload() default {}; + +} diff --git a/src/main/java/com/otakumap/global/validation/validator/RouteLikeExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/RouteLikeExistValidator.java new file mode 100644 index 00000000..c52ec5c8 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/RouteLikeExistValidator.java @@ -0,0 +1,39 @@ +package com.otakumap.global.validation.validator; + + +import com.otakumap.domain.route_like.service.RouteLikeQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistRouteLike; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class RouteLikeExistValidator implements ConstraintValidator> { + private final RouteLikeQueryService routeLikeQueryService; + + @Override + public void initialize(ExistRouteLike constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List routeIds, ConstraintValidatorContext context) { + if (routeIds == null || routeIds.isEmpty()) { + return false; + } + + boolean isValid = routeIds.stream() + .allMatch(routeLikeQueryService::isRouteLikeExist); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.ROUTE_LIKE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} \ No newline at end of file From 9bbeb2acb01af297536092c7344dba07c7ea6f2a Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 24 Jan 2025 23:53:58 +0900 Subject: [PATCH 179/516] =?UTF-8?q?Refactor:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/service/RouteLikeCommandServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index ef792974..3d432e33 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -6,7 +6,6 @@ import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.route_like.repository.RouteLikeRepository; import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.RouteHandler; import jakarta.persistence.EntityManager; From bdef1fc47c43b8905f5e08a31f45c4ab2b2d71e2 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sat, 25 Jan 2025 00:21:15 +0900 Subject: [PATCH 180/516] =?UTF-8?q?Feat:=20=EB=AA=A8=EB=93=A0=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_review/repository/PlaceReviewRepository.java | 2 ++ .../place_review/service/PlaceReviewCommandService.java | 2 ++ .../service/PlaceReviewCommandServiceImpl.java | 6 ++++++ .../otakumap/domain/user/contoller/UserController.java | 9 +++++++++ 4 files changed, 19 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index a0970684..0d116547 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -1,10 +1,12 @@ package com.otakumap.domain.place_review.repository; import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; public interface PlaceReviewRepository extends JpaRepository { Page findAllByUserId(Long userId, PageRequest pageRequest); + void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java index bcea5f22..4657b61d 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -2,7 +2,9 @@ import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.user.entity.User; public interface PlaceReviewCommandService { PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); + void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java index 486b442c..79cd5240 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -35,4 +35,10 @@ public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO req return placeReviewRepository.save(placeReview); } + + @Override + @Transactional + public void deleteAllByUserId(Long userId) { + placeReviewRepository.deleteAllByUserId(userId); + } } diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index ea78d168..6416e899 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -2,6 +2,7 @@ import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.service.PlaceReviewCommandService; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.dto.UserResponseDTO; @@ -24,6 +25,7 @@ public class UserController { private final UserQueryService userQueryService; private final UserCommandService userCommandService; + private final PlaceReviewCommandService placeReviewCommandService; @GetMapping @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") @@ -77,4 +79,11 @@ public ApiResponse resetPassword(@RequestBody @Valid UserRequestDTO.Rese userCommandService.resetPassword(request); return ApiResponse.onSuccess("비밀번호가 성공적으로 변경되었습니다."); } + + @DeleteMapping("/reviews") + @Operation(summary = "내가 작성한 후기 삭제", description = "내가 작성한 모든 후기를 삭제합니다.") + public ApiResponse deleteAllReviews(@CurrentUser User user) { + placeReviewCommandService.deleteAllByUserId(user.getId()); + return ApiResponse.onSuccess("모든 후기가 성공적으로 삭제되었습니다."); + } } From cdd8eadcaa4d40112cb877b4c71bd7db8293ab68 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 03:50:21 +0900 Subject: [PATCH 181/516] =?UTF-8?q?Feat:=20ErrorStatus,=20SortHandler=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/apiPayload/code/status/ErrorStatus.java | 6 ++++-- .../apiPayload/exception/handler/SortHandler.java | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/SortHandler.java diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 213892b7..9e8622e5 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -58,12 +58,14 @@ public enum ErrorStatus implements BaseErrorCode { // 루트 좋아요 관련 에러 ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."), - // 알림 관련 에러 INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."), NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION4002", "존재하지 않는 알림입니다."), NOTIFICATION_ALREADY_READ(HttpStatus.BAD_REQUEST, "NOTIFICATION4003", "이미 읽은 알림입니다."), - NOTIFICATION_ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "NOTIFICATION4004", "알림에 접근할 수 없습니다."); + NOTIFICATION_ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "NOTIFICATION4004", "알림에 접근할 수 없습니다."), + + // 정렬 관련 에러 + INVALID_SORT_TYPE(HttpStatus.BAD_REQUEST, "SORT4001", "유효하지 않은 정렬 기준입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/SortHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/SortHandler.java new file mode 100644 index 00000000..def14c6e --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/SortHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class SortHandler extends GeneralException { + public SortHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From c62aed7d3121744e8d5e21ced9efdd7d9324989c Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 03:54:38 +0900 Subject: [PATCH 182/516] =?UTF-8?q?Rename:=20RouteItem=20Enums=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route_item/entity/RouteItem.java | 2 +- .../otakumap/domain/{route => route_item}/enums/ItemType.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/com/otakumap/domain/{route => route_item}/enums/ItemType.java (50%) diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index da93c54f..8258363f 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -1,7 +1,7 @@ package com.otakumap.domain.route_item.entity; import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route.enums.ItemType; +import com.otakumap.domain.route_item.enums.ItemType; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/otakumap/domain/route/enums/ItemType.java b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java similarity index 50% rename from src/main/java/com/otakumap/domain/route/enums/ItemType.java rename to src/main/java/com/otakumap/domain/route_item/enums/ItemType.java index a49eb9a4..a6e11057 100644 --- a/src/main/java/com/otakumap/domain/route/enums/ItemType.java +++ b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.route.enums; +package com.otakumap.domain.route_item.enums; public enum ItemType { PLACE, From f0cadd23de6ed53052d6deb2c1a38b9d3ffcf025 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 03:55:37 +0900 Subject: [PATCH 183/516] =?UTF-8?q?Feat:=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9,=20=EC=A1=B0=ED=9A=8C=EC=88=98/=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=EC=88=9C=20=EC=A0=95=EB=A0=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceReviewConverter.java | 9 ++---- .../dto/PlaceReviewResponseDTO.java | 17 ++-------- .../PlaceReviewRepositoryCustom.java | 4 +-- .../repository/PlaceReviewRepositoryImpl.java | 32 ++++++++++++++----- .../service/PlaceReviewQueryServiceImpl.java | 29 +++++++++++++++-- 5 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index add46eee..b122f9e1 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -10,7 +10,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Map; public class PlaceReviewConverter { public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { @@ -56,20 +55,18 @@ public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGr .animationId(animation.getId()) .animationName(animation.getName()) .reviews(reviewDTOs) + .totalReviews(reviews.size()) .build(); } // 최상위 결과 DTO 생성 - public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, Map> reviewsByAnimation) { - - List animationGroups = reviewsByAnimation.entrySet().stream() - .map(entry -> toAnimationReviewGroupDTO(entry.getKey(), entry.getValue())) - .toList(); + public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, long totalReviews, List animationGroups) { return PlaceReviewResponseDTO.PlaceAnimationReviewDTO.builder() .placeId(place.getId()) .placeName(place.getName()) .animationGroups(animationGroups) + .totalReviews(totalReviews) .build(); } } diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 980d5748..a819cec7 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -27,7 +27,7 @@ public static class ReviewCreateResponseDTO { @AllArgsConstructor public static class PlaceReviewDTO { private Long reviewId; - private Long placeId; // 나중에 삭제 + private Long placeId; String title; String content; Long view; @@ -43,6 +43,7 @@ public static class AnimationReviewGroupDTO { private Long animationId; private String animationName; private List reviews; + private long totalReviews; } @Builder @@ -53,19 +54,7 @@ public static class PlaceAnimationReviewDTO { private Long placeId; private String placeName; private List animationGroups; + private long totalReviews; } - -// @Builder -// @Getter -// @NoArgsConstructor -// @AllArgsConstructor -// public static class PlaceAnimationReviewListDTO { -// private Long placeId; -// private Integer currentPage; -// private Integer totalPages; -// private Integer totalElements; -// private List animationReviews; -// } - } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java index 7747b552..b6587d5a 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryCustom.java @@ -1,12 +1,10 @@ package com.otakumap.domain.place_review.repository; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.place_review.entity.PlaceReview; import java.util.List; -import java.util.Map; public interface PlaceReviewRepositoryCustom { - Map> findReviewsByPlaceWithAnimation(Long placeId, int page, int size, String sort); + List findAllReviewsByPlace(Long placeId, String sort); } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java index b264ca99..6d4acb92 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java @@ -1,18 +1,18 @@ package com.otakumap.domain.place_review.repository; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.entity.QAnimation; import com.otakumap.domain.mapping.QPlaceAnimation; import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.SortHandler; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @Repository @RequiredArgsConstructor @@ -21,23 +21,39 @@ public class PlaceReviewRepositoryImpl implements PlaceReviewRepositoryCustom { private final JPAQueryFactory queryFactory; @Override - public Map> findReviewsByPlaceWithAnimation(Long placeId, int page, int size, String sort) { + public List findAllReviewsByPlace(Long placeId, String sort) { QPlaceReview placeReview = QPlaceReview.placeReview; QAnimation animation = QAnimation.animation; QPlaceAnimation placeAnimation = QPlaceAnimation.placeAnimation; QPlace place = QPlace.place; + // 동적 정렬 조건 생성 + OrderSpecifier[] orderBy = getOrderSpecifier(placeReview, sort); - List reviews = queryFactory.selectFrom(placeReview) + return queryFactory.selectFrom(placeReview) .join(placeReview.place, place) // PlaceReview에서 Place로 조인 .join(place.placeAnimationList, placeAnimation) // Place에서 PlaceAnimation으로 조인 .join(placeAnimation.animation, animation) // PlaceAnimation에서 Animation으로 조인 .where(placeReview.place.id.eq(placeId)) + .orderBy(orderBy) .fetch(); + } - // Animation을 기준으로 그룹화 - return reviews.stream() - .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); + private OrderSpecifier[] getOrderSpecifier(QPlaceReview placeReview, String sort) { + + if ("views".equals(sort)) { + return new OrderSpecifier[]{ + placeReview.view.desc(), + placeReview.createdAt.desc() // 조회수가 동일하면 최신순으로 정렬 + }; + } else if ("latest".equals(sort)) { + return new OrderSpecifier[]{ + placeReview.createdAt.desc(), + placeReview.view.desc() // 최신순이 동일하면 조회수순으로 정렬 + }; + } else { + throw new SortHandler(ErrorStatus.INVALID_SORT_TYPE); + } } } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index fd6097ec..cae10b70 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -30,8 +31,32 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - Map> reviewsByAnimation = placeReviewRepository.findReviewsByPlaceWithAnimation(placeId, page, size, sort); + // 전체 리뷰 리스트 + List allReviews = placeReviewRepository.findAllReviewsByPlace(placeId, sort); - return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, reviewsByAnimation); + // 애니메이션별로 그룹화 + Map> reviewsByAnimation = allReviews.stream() + .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); + + // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 + List animationGroups = reviewsByAnimation.entrySet().stream() + .map(entry -> { + List reviews = entry.getValue(); + int fromIndex = Math.min(page * size, reviews.size()); + int toIndex = Math.min(fromIndex + size, reviews.size()); + + List pagedReviews = reviews.subList(fromIndex, toIndex); + + return PlaceReviewConverter.toAnimationReviewGroupDTO(entry.getKey(), pagedReviews); + }) + .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 + .toList(); + + // 총 리뷰 수 계산 + long totalReviews = reviewsByAnimation.values().stream() + .mapToLong(List::size) + .sum(); + + return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups); } } From a280e3f0812d6ace4f238e3455b30a3cd013f02e Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 04:39:19 +0900 Subject: [PATCH 184/516] =?UTF-8?q?Feat:=20HashTag,=20AnimationHashTag=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/entity/Animation.java | 9 ++++++- .../domain/hash_tag/entity/HashTag.java | 20 ++++++++++++++ .../domain/mapping/AnimationHashTag.java | 27 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java create mode 100644 src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index e62c9c1c..ae6b919e 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -1,9 +1,13 @@ package com.otakumap.domain.animation.entity; +import com.otakumap.domain.mapping.AnimationHashTag; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @@ -15,6 +19,9 @@ public class Animation extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) // 이벤트 일본어 원제 + @Column(nullable = false, length = 20) private String name; + + @OneToMany(mappedBy = "animation", cascade = CascadeType.ALL) + private List animationHashTagList = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java b/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java new file mode 100644 index 00000000..b70b56c0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.hash_tag.entity; + +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class HashTag extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String name; +} diff --git a/src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java b/src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java new file mode 100644 index 00000000..ec0fedc7 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class AnimationHashTag extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "animation_id") + private Animation animation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hash_tag_id") + private HashTag hashTag; +} From ee4ce5135bc374341f8b171607cd1752b8fb5e21 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 04:52:24 +0900 Subject: [PATCH 185/516] =?UTF-8?q?Feat:=20EventHashTag,=20PlaceHashTag=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/entity/Event.java | 4 +++ .../otakumap/domain/mapping/EventHashTag.java | 27 +++++++++++++++++++ .../otakumap/domain/mapping/PlaceHashTag.java | 27 +++++++++++++++++++ .../otakumap/domain/place/entity/Place.java | 4 +++ 4 files changed, 62 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/mapping/EventHashTag.java create mode 100644 src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index 06af4de8..ebe0c7a8 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -7,6 +7,7 @@ import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.mapping.EventHashTag; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -78,4 +79,7 @@ public class Event extends BaseEntity { @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) private List eventAnimationList = new ArrayList<>(); + + @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) + private List eventHashTagList = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/mapping/EventHashTag.java b/src/main/java/com/otakumap/domain/mapping/EventHashTag.java new file mode 100644 index 00000000..2c895d0e --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/EventHashTag.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventHashTag extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id") + private Event event; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hash_tag_id") + private HashTag hashTag; +} diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java b/src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java new file mode 100644 index 00000000..eb6f5ffd --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceHashTag extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hash_tag_id") + private HashTag hashTag; +} diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index e6ae8e5a..2e6db0ae 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place.entity; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceHashTag; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -46,4 +47,7 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List placeHashTagList = new ArrayList<>(); } From 0cd1ef4aa4b0235125b1c1f878eca96ba2bd5ba8 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 04:54:55 +0900 Subject: [PATCH 186/516] =?UTF-8?q?Remove:=20AnimationHashTag=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/entity/Animation.java | 7 ----- .../domain/mapping/AnimationHashTag.java | 27 ------------------- 2 files changed, 34 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index ae6b919e..1259b0c9 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -1,13 +1,9 @@ package com.otakumap.domain.animation.entity; -import com.otakumap.domain.mapping.AnimationHashTag; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @Builder @@ -21,7 +17,4 @@ public class Animation extends BaseEntity { @Column(nullable = false, length = 20) private String name; - - @OneToMany(mappedBy = "animation", cascade = CascadeType.ALL) - private List animationHashTagList = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java b/src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java deleted file mode 100644 index ec0fedc7..00000000 --- a/src/main/java/com/otakumap/domain/mapping/AnimationHashTag.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.otakumap.domain.mapping; - -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.hash_tag.entity.HashTag; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class AnimationHashTag extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "animation_id") - private Animation animation; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "hash_tag_id") - private HashTag hashTag; -} From cb2c87281a73220ada42f6577438157c428167e4 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 06:28:05 +0900 Subject: [PATCH 187/516] =?UTF-8?q?Feat:=20DTO=EC=97=90=20=ED=95=B4?= =?UTF-8?q?=EC=8B=9C=ED=83=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_review/converter/PlaceReviewConverter.java | 9 +++++++++ .../domain/place_review/dto/PlaceReviewResponseDTO.java | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index b122f9e1..78924ff1 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -1,6 +1,8 @@ package com.otakumap.domain.place_review.converter; import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; @@ -33,6 +35,7 @@ public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateReques // PlaceReview -> PlaceReviewDTO 변환 public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { + return PlaceReviewResponseDTO.PlaceReviewDTO.builder() .reviewId(placeReview.getId()) .placeId(placeReview.getPlace().getId()) @@ -62,11 +65,17 @@ public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGr // 최상위 결과 DTO 생성 public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, long totalReviews, List animationGroups) { + List hashTagDTOs = place.getPlaceHashTagList() + .stream() + .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())) + .toList(); + return PlaceReviewResponseDTO.PlaceAnimationReviewDTO.builder() .placeId(place.getId()) .placeName(place.getName()) .animationGroups(animationGroups) .totalReviews(totalReviews) + .hashTags(hashTagDTOs) .build(); } } diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index a819cec7..fddf1f54 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_review.dto; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -53,6 +54,7 @@ public static class AnimationReviewGroupDTO { public static class PlaceAnimationReviewDTO { private Long placeId; private String placeName; + List hashTags; private List animationGroups; private long totalReviews; } From 28caf7cc940ce437d21c1b8564a18ff1c3c1106c Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 06:29:14 +0900 Subject: [PATCH 188/516] =?UTF-8?q?Feat:=20HashTag=20DTO,=20Converter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hash_tag/converter/HashTagConverter.java | 14 ++++++++++++++ .../hash_tag/dto/HashTagResponseDTO.java | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java create mode 100644 src/main/java/com/otakumap/domain/hash_tag/dto/HashTagResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java b/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java new file mode 100644 index 00000000..5ecf08e6 --- /dev/null +++ b/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.hash_tag.converter; + +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +import com.otakumap.domain.hash_tag.entity.HashTag; + +public class HashTagConverter { + + public static HashTagResponseDTO.HashTagDTO toHashTagDTO(HashTag hashTag) { + return HashTagResponseDTO.HashTagDTO.builder() + .hashTagId(hashTag.getId()) + .name(hashTag.getName()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/hash_tag/dto/HashTagResponseDTO.java b/src/main/java/com/otakumap/domain/hash_tag/dto/HashTagResponseDTO.java new file mode 100644 index 00000000..8f1b37cd --- /dev/null +++ b/src/main/java/com/otakumap/domain/hash_tag/dto/HashTagResponseDTO.java @@ -0,0 +1,18 @@ +package com.otakumap.domain.hash_tag.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class HashTagResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HashTagDTO { + private Long hashTagId; + private String name; + } +} From 225738363af582efb1224c263204838d1f87e256 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 06:35:35 +0900 Subject: [PATCH 189/516] =?UTF-8?q?Refactor:=20DTO=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=EC=9E=90=20private=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_review/dto/PlaceReviewResponseDTO.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index fddf1f54..349400f7 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -29,11 +29,11 @@ public static class ReviewCreateResponseDTO { public static class PlaceReviewDTO { private Long reviewId; private Long placeId; - String title; - String content; - Long view; - LocalDateTime createdAt; - ImageResponseDTO.ImageDTO reviewImage; + private String title; + private String content; + private Long view; + private LocalDateTime createdAt; + private ImageResponseDTO.ImageDTO reviewImage; } @Builder @@ -54,7 +54,7 @@ public static class AnimationReviewGroupDTO { public static class PlaceAnimationReviewDTO { private Long placeId; private String placeName; - List hashTags; + private List hashTags; private List animationGroups; private long totalReviews; } From ca95ea3bee607fda367532db2267dbff6d4a4314 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 07:20:07 +0900 Subject: [PATCH 190/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=EB=A1=9C=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=B0=BE=EA=B8=B0?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceShortReviewRepository.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java index 71f6ccda..9bdf1859 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java @@ -6,6 +6,10 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface PlaceShortReviewRepository extends JpaRepository { Page findAllByPlace(Place place, PageRequest pageRequest); + + List findAllByPlace(Place place); } From 68b727ac7c9fd2c07e5a721503abf78f7e77c001 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 07:21:50 +0900 Subject: [PATCH 191/516] =?UTF-8?q?Feat:=20PlaceAnimationReviewDTO?= =?UTF-8?q?=EC=97=90=20=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=ED=8F=89=EA=B7=A0=20=EC=A0=90=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_review/converter/PlaceReviewConverter.java | 5 ++++- .../domain/place_review/dto/PlaceReviewResponseDTO.java | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 78924ff1..548d3b7a 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -63,7 +63,9 @@ public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGr } // 최상위 결과 DTO 생성 - public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, long totalReviews, List animationGroups) { + public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, long totalReviews, + List animationGroups, + Float avgRating) { List hashTagDTOs = place.getPlaceHashTagList() .stream() @@ -76,6 +78,7 @@ public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationRev .animationGroups(animationGroups) .totalReviews(totalReviews) .hashTags(hashTagDTOs) + .avgRating(avgRating) .build(); } } diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 349400f7..196fba44 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -54,9 +54,10 @@ public static class AnimationReviewGroupDTO { public static class PlaceAnimationReviewDTO { private Long placeId; private String placeName; + private Float avgRating; + private long totalReviews; private List hashTags; private List animationGroups; - private long totalReviews; } } From d6ef98772be1c63fd00a115cae4d8931105fb8c7 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 07:22:31 +0900 Subject: [PATCH 192/516] =?UTF-8?q?Feat:=20=ED=95=9C=20=EC=A4=84=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=ED=8F=89=EA=B7=A0=20=EC=A0=90=EC=88=98=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PlaceReviewQueryServiceImpl.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index cae10b70..941f87dc 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -7,6 +7,8 @@ import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import lombok.RequiredArgsConstructor; @@ -24,6 +26,7 @@ public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { private final PlaceRepository placeRepository; private final PlaceReviewRepository placeReviewRepository; + private final PlaceShortReviewRepository placeShortReviewRepository; @Override public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { @@ -31,6 +34,14 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + // 전체 한 줄 리뷰 평균 별점 구하기 + List placeShortReviewList = placeShortReviewRepository.findAllByPlace(place); + double avgRating = placeShortReviewList.stream() + .mapToDouble(PlaceShortReview::getRating) + .average() + .orElse(0.0); + Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); + // 전체 리뷰 리스트 List allReviews = placeReviewRepository.findAllReviewsByPlace(placeId, sort); @@ -57,6 +68,6 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla .mapToLong(List::size) .sum(); - return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups); + return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); } } From 4f378b671e5fdd48df00200b44142c0469a4fc9e Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:02:58 +0900 Subject: [PATCH 193/516] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PlaceReviewQueryServiceImpl.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 941f87dc..0b3a234c 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -50,7 +50,19 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 - List animationGroups = reviewsByAnimation.entrySet().stream() + List animationGroups = paginateReviews(reviewsByAnimation, page, size); + + // 총 리뷰 수 계산 + long totalReviews = reviewsByAnimation.values().stream() + .mapToLong(List::size) + .sum(); + + return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); + } + + private List paginateReviews(Map> reviewsByAnimation, int page, int size) { + + return reviewsByAnimation.entrySet().stream() .map(entry -> { List reviews = entry.getValue(); int fromIndex = Math.min(page * size, reviews.size()); @@ -62,12 +74,5 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla }) .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 .toList(); - - // 총 리뷰 수 계산 - long totalReviews = reviewsByAnimation.values().stream() - .mapToLong(List::size) - .sum(); - - return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); } } From 9379cfdcba2afc908befccaf56071ca41f0ca495 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 25 Jan 2025 20:57:26 +0900 Subject: [PATCH 194/516] =?UTF-8?q?Refactor:=20API=20Endpoint=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/controller/EventLikeController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 9e774f4c..b43f0fa1 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -17,7 +17,7 @@ import java.util.List; @RestController -@RequestMapping("/api/events") +@RequestMapping("/api/event-likes") @RequiredArgsConstructor @Validated public class EventLikeController { @@ -35,7 +35,7 @@ public ApiResponse postPlaceLike(@CurrentUser User user, @PathVariable L } @Operation(summary = "저장된 이벤트 목록 조회", description = "저장된 이벤트 목록을 불러옵니다.") - @GetMapping( "/liked") + @GetMapping( "") @Parameters({ @Parameter(name = "type", description = "이벤트 타입 -> 1: 팝업 스토어, 2: 전시회, 3: 콜라보 카페"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @@ -46,7 +46,7 @@ public ApiResponse getEventLikeLis } @Operation(summary = "저장된 이벤트 삭제", description = "저장된 이벤트를 삭제합니다.") - @DeleteMapping("/liked") + @DeleteMapping("") @Parameters({ @Parameter(name = "eventIds", description = "저장된 이벤트 ID List"), }) From 94fd39208d5e31d80fc7b193d13531be23ad904d Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 25 Jan 2025 23:25:57 +0900 Subject: [PATCH 195/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0/=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventLikeController.java | 11 ++++++++++- .../converter/EventLikeConverter.java | 13 ++++++++++--- .../event_like/dto/EventLikeRequestDTO.java | 13 +++++++++++++ .../event_like/dto/EventLikeResponseDTO.java | 11 ++++++++++- .../domain/event_like/entity/EventLike.java | 8 ++++++-- .../service/EventLikeCommandService.java | 3 +++ .../service/EventLikeCommandServiceImpl.java | 19 ++++++++++++------- 7 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index b43f0fa1..04849dc9 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -1,6 +1,8 @@ package com.otakumap.domain.event_like.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.event_like.converter.EventLikeConverter; +import com.otakumap.domain.event_like.dto.EventLikeRequestDTO; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.service.EventLikeCommandService; import com.otakumap.domain.event_like.service.EventLikeQueryService; @@ -10,6 +12,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -45,7 +48,7 @@ public ApiResponse getEventLikeLis return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(user, type, lastId, limit)); } - @Operation(summary = "저장된 이벤트 삭제", description = "저장된 이벤트를 삭제합니다.") + @Operation(summary = "저장된 이벤트 삭제(이벤트 찜하기 취소)", description = "저장된 이벤트를 삭제합니다.") @DeleteMapping("") @Parameters({ @Parameter(name = "eventIds", description = "저장된 이벤트 ID List"), @@ -54,4 +57,10 @@ public ApiResponse deleteEventLike(@RequestParam(required = false) @Exis eventLikeCommandService.deleteEventLike(eventIds); return ApiResponse.onSuccess("저장된 이벤트가 성공적으로 삭제되었습니다"); } + + @Operation(summary = "저장된 이벤트 즐겨찾기/즐겨찾기 취소", description = "저장된 이벤트를 즐겨찾기 또는 취소합니다.") + @PatchMapping("/{eventLikeId}/bookmark") + public ApiResponse bookmarkEventLike(@PathVariable Long eventLikeId, @RequestBody @Valid EventLikeRequestDTO.BookmarkDTO request) { + return ApiResponse.onSuccess(EventLikeConverter.toBookmarkResultDTO(eventLikeCommandService.bookmarkEventLike(eventLikeId, request))); + } } diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index eeb20a4e..8d10cf4e 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -17,7 +17,7 @@ public static EventLikeResponseDTO.EventLikePreViewDTO eventLikePreViewDTO(Event .thumbnail(eventLike.getEvent().getThumbnailImage().getFileUrl()) .startDate(eventLike.getEvent().getStartDate()) .endDate(eventLike.getEvent().getEndDate()) - .isFavorite(eventLike.getIsFavorite()) + .isBookmarked(eventLike.getIsBookmarked()) .eventType(eventLike.getEvent().getType()) .build(); @@ -30,11 +30,18 @@ public static EventLikeResponseDTO.EventLikePreViewListDTO eventLikePreViewListD .build(); } - public static EventLike eventLike(User user, Event event) { + public static EventLike toEventLike(User user, Event event) { return EventLike.builder() .event(event) .user(user) - .isFavorite(true) + .isBookmarked(false) + .build(); + } + + public static EventLikeResponseDTO.BookmarkResultDTO toBookmarkResultDTO(EventLike eventLike) { + return EventLikeResponseDTO.BookmarkResultDTO.builder() + .eventLikeId(eventLike.getId()) + .isBookmarked(eventLike.getIsBookmarked()) .build(); } } diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java new file mode 100644 index 00000000..cdde86c5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java @@ -0,0 +1,13 @@ +package com.otakumap.domain.event_like.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class EventLikeRequestDTO { + @Getter + public static class BookmarkDTO { + @NotNull(message = "북마크 여부 입력은 필수입니다.") + Boolean isBookmarked; + } +} diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java index 40a7c02e..0677cb7f 100644 --- a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java @@ -21,7 +21,7 @@ public static class EventLikePreViewDTO { String thumbnail; LocalDate startDate; LocalDate endDate; - Boolean isFavorite; + boolean isBookmarked; EventType eventType; } @@ -34,4 +34,13 @@ public static class EventLikePreViewListDTO { boolean hasNext; Long lastId; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class BookmarkResultDTO { + Long eventLikeId; + Boolean isBookmarked; + } } diff --git a/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java b/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java index f21e906b..a78f1af2 100644 --- a/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java +++ b/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java @@ -29,7 +29,11 @@ public class EventLike extends BaseEntity { @JoinColumn(name = "event_id", nullable = false) private Event event; - @Column(name = "is_favorite", nullable = false) + @Column(name = "is_bookmarked", nullable = false) @ColumnDefault("false") - private Boolean isFavorite; + private Boolean isBookmarked; + + public void setIsBookmarked(boolean isBookmarked) { + this.isBookmarked = isBookmarked; + } } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java index c8e7502d..34335794 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java @@ -1,5 +1,7 @@ package com.otakumap.domain.event_like.service; +import com.otakumap.domain.event_like.dto.EventLikeRequestDTO; +import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.user.entity.User; import java.util.List; @@ -7,4 +9,5 @@ public interface EventLikeCommandService { void addEventLike(User user, Long eventId); void deleteEventLike(List eventIds); + EventLike bookmarkEventLike(Long eventLikeId, EventLikeRequestDTO.BookmarkDTO request); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index 85d34b21..00561367 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -3,6 +3,9 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; import com.otakumap.domain.event_like.converter.EventLikeConverter; +import com.otakumap.domain.event_like.dto.EventLikeRequestDTO; +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; @@ -24,13 +27,8 @@ public class EventLikeCommandServiceImpl implements EventLikeCommandService { @Override public void addEventLike(User user, Long eventId) { - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); - eventLikeRepository.save( - EventLikeConverter.eventLike(user, event) - ); - entityManager.flush(); - entityManager.clear(); + Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + eventLikeRepository.save(EventLikeConverter.toEventLike(user, event)); } @Override @@ -39,4 +37,11 @@ public void deleteEventLike(List eventIds) { entityManager.flush(); entityManager.clear(); } + + @Override + public EventLike bookmarkEventLike(Long eventLikeId, EventLikeRequestDTO.BookmarkDTO request) { + EventLike eventLike = eventLikeRepository.findById(eventLikeId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_LIKE_NOT_FOUND)); + eventLike.setIsBookmarked(request.getIsBookmarked()); + return eventLikeRepository.save(eventLike); + } } From 0647f286a7cdb4ee3b2f0a57d6cd36e6e32fe5dd Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 26 Jan 2025 13:00:07 +0900 Subject: [PATCH 196/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventLikeController.java | 7 +-- .../service/EventLikeQueryService.java | 2 +- .../service/EventLikeQueryServiceImpl.java | 49 ++++++++++++------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 04849dc9..9c5a6e18 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -37,15 +37,16 @@ public ApiResponse postPlaceLike(@CurrentUser User user, @PathVariable L return ApiResponse.onSuccess("이벤트가 성공적으로 저장되었습니다."); } - @Operation(summary = "저장된 이벤트 목록 조회", description = "저장된 이벤트 목록을 불러옵니다.") + @Operation(summary = "저장된 이벤트 목록 조회(+ 즐겨찾기 목록 조회)", description = "저장된 이벤트 목록을 불러옵니다.") @GetMapping( "") @Parameters({ @Parameter(name = "type", description = "이벤트 타입 -> 1: 팝업 스토어, 2: 전시회, 3: 콜라보 카페"), + @Parameter(name = "isBookmarked", description = "북마크 여부(필수 X) -> true: 북마크 목록 조회"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") }) - public ApiResponse getEventLikeList(@CurrentUser User user, @RequestParam(required = false) Integer type, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { - return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(user, type, lastId, limit)); + public ApiResponse getEventLikeList(@CurrentUser User user, @RequestParam(required = false) Integer type, @RequestParam(required = false) Boolean isBookmarked, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(user, type, isBookmarked, lastId, limit)); } @Operation(summary = "저장된 이벤트 삭제(이벤트 찜하기 취소)", description = "저장된 이벤트를 삭제합니다.") diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java index fe3b4873..4d38ba75 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java @@ -4,6 +4,6 @@ import com.otakumap.domain.user.entity.User; public interface EventLikeQueryService { - EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Long lastId, int limit); + EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Boolean isBookmarked, Long lastId, int limit); boolean isEventLikeExist(Long id); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java index 62757f9e..03d24375 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java @@ -4,15 +4,12 @@ import com.otakumap.domain.event_like.converter.EventLikeConverter; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.event_like.entity.QEventLike; import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.EventHandler; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,24 +21,38 @@ @Transactional(readOnly = true) public class EventLikeQueryServiceImpl implements EventLikeQueryService { private final EventLikeRepository eventLikeRepository; - private final UserRepository userRepository; + private final JPAQueryFactory jpaQueryFactory; @Override - public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Long lastId, int limit) { - List result; - Pageable pageable = PageRequest.of(0, limit + 1); + public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Boolean isBookmarked, Long lastId, int limit) { EventType eventType = (type == null || type == 0) ? null : EventType.values()[type - 1]; - if (lastId.equals(0L)) { - result = (eventType == null) - ? eventLikeRepository.findAllByUserIsOrderByCreatedAtDesc(user, pageable).getContent() - : eventLikeRepository.findAllByUserIsAndEventTypeOrderByCreatedAtDesc(user, eventType, pageable).getContent(); - } else { - EventLike eventLike = eventLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_LIKE_NOT_FOUND)); - result = (eventType == null) - ? eventLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventLike.getCreatedAt(), pageable).getContent() - : eventLikeRepository.findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(user, eventType, eventLike.getCreatedAt(), pageable).getContent(); + QEventLike qEventLike = QEventLike.eventLike; + BooleanBuilder predicate = new BooleanBuilder(); + + predicate.and(qEventLike.user.eq(user)); + + if (eventType != null) { + predicate.and(qEventLike.event.type.eq(eventType)); + } + + if (isBookmarked != null) { + predicate.and(qEventLike.isBookmarked.eq(isBookmarked)); } + + if (lastId != null && lastId > 0) { + predicate.and(qEventLike.id.lt(lastId)); + } + + List result = jpaQueryFactory + .selectFrom(qEventLike) + .leftJoin(qEventLike.event).fetchJoin() + .leftJoin(qEventLike.user).fetchJoin() + .where(predicate) + .orderBy(qEventLike.createdAt.desc()) + .limit(limit + 1) + .fetch(); + return createEventLikePreviewListDTO(result, limit); } From 1a29bda1fef02550de6530cacb122e5c05578282 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sun, 26 Jan 2025 18:16:45 +0900 Subject: [PATCH 197/516] =?UTF-8?q?Fix:=20CORS=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EA=B0=80=20=ED=95=84=ED=84=B0=20=EC=B2=B4=EC=9D=B8=EA=B3=BC=20?= =?UTF-8?q?=EB=AC=B4=EA=B4=80=ED=95=9C=20=EA=B2=BD=EC=9A=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/global/config/WebConfig.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java index 173e0364..4b91a2ac 100644 --- a/src/main/java/com/otakumap/global/config/WebConfig.java +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @@ -20,6 +21,17 @@ public void addArgumentResolvers(List resolvers) resolvers.add(authenticatedUserResolver); } + // CORS 처리가 필터 체인과 무관한 경우 + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:3000") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } + @Bean public RestTemplate restTemplate() { return new RestTemplate(); From 0cb32130bf2f3f229cfcbdf7536843443d24ea1a Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sun, 26 Jan 2025 18:19:05 +0900 Subject: [PATCH 198/516] =?UTF-8?q?Fix:=20spring=20security=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B1=B8=EB=A6=AC=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/global/config/CorsConfig.java | 26 +++++++++++++++++++ .../global/config/SecurityConfig.java | 3 +++ 2 files changed, 29 insertions(+) create mode 100644 src/main/java/com/otakumap/global/config/CorsConfig.java diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java new file mode 100644 index 00000000..a7fcab11 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -0,0 +1,26 @@ +package com.otakumap.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; + +@Configuration +public class CorsConfig { + + @Bean + public static CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index a45ce3e8..67c1fb41 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -41,6 +41,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http //crsf 보안 비활성화 .csrf(csrf -> csrf.disable()) + // spring security에서 걸리는 경우 + .cors(cors -> cors + .configurationSource(CorsConfig.corsConfigurationSource())) .authorizeHttpRequests(request -> request .requestMatchers(allowUrl).permitAll() .anyRequest().authenticated()) From 0032677a3195f9770b089d9009765d8c274eb98d Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sun, 26 Jan 2025 18:20:45 +0900 Subject: [PATCH 199/516] =?UTF-8?q?Fix:=20spring=20security=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B1=B8=EB=A6=AC=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/CorsConfig.java | 5 +++-- src/main/java/com/otakumap/global/config/SecurityConfig.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java index a7fcab11..6d65e335 100644 --- a/src/main/java/com/otakumap/global/config/CorsConfig.java +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -7,6 +7,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; +import java.util.List; @Configuration public class CorsConfig { @@ -14,9 +15,9 @@ public class CorsConfig { @Bean public static CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); + configuration.setAllowedOrigins(List.of("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); - configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 67c1fb41..97cb0c57 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -40,7 +40,7 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http //crsf 보안 비활성화 - .csrf(csrf -> csrf.disable()) + .csrf(AbstractHttpConfigurer::disable) // spring security에서 걸리는 경우 .cors(cors -> cors .configurationSource(CorsConfig.corsConfigurationSource())) @@ -48,7 +48,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(allowUrl).permitAll() .anyRequest().authenticated()) //기본 폼 로그인 비활성화 - .formLogin((form) -> form.disable()) + .formLogin(AbstractHttpConfigurer::disable) // BasicHttp 비활성화 .httpBasic(AbstractHttpConfigurer::disable) //JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가 From 8276955827a5ab37410a861aafd05c87e1cdf849 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 26 Jan 2025 21:08:44 +0900 Subject: [PATCH 200/516] =?UTF-8?q?Feat:=20=EA=B5=AC=EA=B8=80=20=EB=A7=B5?= =?UTF-8?q?=20=EC=9E=A5=EC=86=8C=20=EA=B2=80=EC=83=89=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84(=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++ .../place/controller/PlaceController.java | 12 ++++++ .../domain/place/dto/PlaceResponseDTO.java | 39 +++++++++++++++++++ .../place/service/GooglePlacesService.java | 38 ++++++++++++++++++ .../global/config/GoogleMapsProperties.java | 17 ++++++++ 5 files changed, 111 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java create mode 100644 src/main/java/com/otakumap/global/config/GoogleMapsProperties.java diff --git a/build.gradle b/build.gradle index 57ae7347..6c69d484 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'com.google.code.gson:gson' + + // 구글 places api 관련 +// implementation 'com.google.maps:google-maps-services:0.42.2' + // Jackson: JSON 응답을 파싱 + implementation 'com.fasterxml.jackson.core:jackson-databind' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java index 306c5c6e..d810ba22 100644 --- a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -1,7 +1,11 @@ package com.otakumap.domain.place.controller; +import com.otakumap.domain.place.dto.PlaceResponseDTO; +import com.otakumap.domain.place.service.GooglePlacesService; import lombok.RequiredArgsConstructor; +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; @RestController @@ -9,4 +13,12 @@ @RequiredArgsConstructor public class PlaceController { + private final GooglePlacesService googlePlacesService; + + + @GetMapping("/places/search") + public PlaceResponseDTO searchPlaces(@RequestParam String query) { + return googlePlacesService.searchPlaces(query); + } + } diff --git a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java new file mode 100644 index 00000000..8c45fc08 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java @@ -0,0 +1,39 @@ +package com.otakumap.domain.place.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; + +@Data +public class PlaceResponseDTO { + + @JsonProperty("results") + private List results; + + @Data + public static class SearchedPlaceDTO { + + @JsonProperty("formatted_address") + private String formattedAddress; + + @JsonProperty("geometry") + private Geometry geometry; + + @JsonProperty("name") + private String name; + + @Data + public static class Geometry { + @JsonProperty("location") + private Location location; + + @Data + public static class Location { + private double lat; + private double lng; + } + } + + } +} diff --git a/src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java b/src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java new file mode 100644 index 00000000..08260962 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.place.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.otakumap.domain.place.dto.PlaceResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +@RequiredArgsConstructor +public class GooglePlacesService { + + @Value("${google.api.key}") + private String apiKey; + + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + public PlaceResponseDTO searchPlaces(String query) { + String url = String.format( + "https://maps.googleapis.com/maps/api/place/textsearch/json?query=%s&key=%s", + query, + apiKey + ); + + // API 호출 + String response = restTemplate.getForObject(url, String.class); + System.out.println(response); // 실제 API 응답 확인 + + try { + // 응답 데이터를 DTO로 변환 + return objectMapper.readValue(response, PlaceResponseDTO.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse Google Places API response", e); + } + } +} diff --git a/src/main/java/com/otakumap/global/config/GoogleMapsProperties.java b/src/main/java/com/otakumap/global/config/GoogleMapsProperties.java new file mode 100644 index 00000000..375307ca --- /dev/null +++ b/src/main/java/com/otakumap/global/config/GoogleMapsProperties.java @@ -0,0 +1,17 @@ +package com.otakumap.global.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "google") +public class GoogleMapsProperties { + private GoogleMapsProperties.ProviderProperties api; + + @Data + public static class ProviderProperties { + private String key; + } +} From 770a9ae228889bb9471ebba0fda12241351395c0 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 27 Jan 2025 16:37:30 +0900 Subject: [PATCH 201/516] =?UTF-8?q?Feat:=20user,=20animation=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceAnimationRepository.java | 7 +++++++ .../DTO/PlaceShortReviewRequestDTO.java | 3 ++- .../DTO/PlaceShortReviewResponseDTO.java | 2 +- .../controller/PlaceShortReviewController.java | 5 ++++- .../converter/PlaceShortReviewConverter.java | 8 +++++--- .../entity/PlaceShortReview.java | 5 +++++ .../service/PlaceShortReviewCommandService.java | 3 ++- .../PlaceShortReviewCommandServiceImpl.java | 16 +++++++++------- .../apiPayload/code/status/ErrorStatus.java | 1 + 9 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java new file mode 100644 index 00000000..dad57dba --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place_animation.repository; + +import com.otakumap.domain.mapping.PlaceAnimation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceAnimationRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewRequestDTO.java index ead8aad2..43210cf8 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewRequestDTO.java @@ -7,7 +7,8 @@ public class PlaceShortReviewRequestDTO { @Getter public static class CreateDTO { - Long userId; // 토큰 사용 전 임시 + @NotNull + Long placeAnimationId; @NotNull Float rating; @NotBlank diff --git a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java index a94daafb..dcfd99cf 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java @@ -56,7 +56,7 @@ public static class CreateReviewDTO { private Float rating; private String content; private LocalDateTime createdAt; - private Long userId; private Long placeId; + private Long placeAnimationId; } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index f6c2c08b..0ec19ef4 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -1,11 +1,13 @@ package com.otakumap.domain.place_short_review.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewResponseDTO; import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; import com.otakumap.domain.place_short_review.service.PlaceShortReviewQueryService; import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.service.PlaceShortReviewCommandService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ExistPlace; import io.swagger.v3.oas.annotations.Operation; @@ -42,9 +44,10 @@ public ApiResponse getPlace @PostMapping("/places/{placeId}/short-review") @Operation(summary = "특정 명소의 한 줄 리뷰 목록 작성 API", description = "특정 명소의 한 줄 리뷰를 작성하는 API입니다.") public ApiResponse createReview( + @CurrentUser User user, @PathVariable Long placeId, @RequestBody @Valid PlaceShortReviewRequestDTO.CreateDTO request) { - PlaceShortReview placeShortReview = placeShortReviewCommandService.createReview(placeId, request); + PlaceShortReview placeShortReview = placeShortReviewCommandService.createReview(user, placeId, request); return ApiResponse.onSuccess(PlaceShortReviewConverter.toCreateReviewDTO(placeShortReview)); } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index b0d2ce41..c7565237 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_short_review.converter; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewResponseDTO; @@ -48,22 +49,23 @@ public static PlaceShortReviewResponseDTO.PlaceShortReviewListDTO placeShortRevi } public static PlaceShortReviewResponseDTO.CreateReviewDTO toCreateReviewDTO(PlaceShortReview placeShortReview) { - User user = placeShortReview.getUser(); Place place = placeShortReview.getPlace(); + PlaceAnimation placeAnimation = placeShortReview.getPlaceAnimation(); return PlaceShortReviewResponseDTO.CreateReviewDTO.builder() .reviewId(placeShortReview.getId()) .rating(placeShortReview.getRating()) .content(placeShortReview.getContent()) .createdAt(placeShortReview.getCreatedAt()) - .userId(user.getId()) .placeId(place.getId()) + .placeAnimationId(placeAnimation.getId()) .build(); } - public static PlaceShortReview toPlaceShortReview(PlaceShortReviewRequestDTO.CreateDTO request, User user, Place place) { + public static PlaceShortReview toPlaceShortReview(PlaceShortReviewRequestDTO.CreateDTO request, User user, Place place, PlaceAnimation placeAnimation) { return PlaceShortReview.builder() .user(user) .place(place) + .placeAnimation(placeAnimation) .rating(request.getRating()) .content(request.getContent()) .dislikes(0L) diff --git a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java index cf5dc001..63dcb3c5 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java +++ b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_short_review.entity; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -35,4 +36,8 @@ public class PlaceShortReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id", nullable = false) private Place place; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_animation_id") + private PlaceAnimation placeAnimation; } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java index 4aa9d5b7..7c21e111 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java @@ -2,7 +2,8 @@ import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.user.entity.User; public interface PlaceShortReviewCommandService { - PlaceShortReview createReview(Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); + PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index 0bcdf9cc..5f1ec37a 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -1,7 +1,9 @@ package com.otakumap.domain.place_short_review.service; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; @@ -18,20 +20,20 @@ @Service @RequiredArgsConstructor public class PlaceShortReviewCommandServiceImpl implements PlaceShortReviewCommandService { - private final PlaceShortReviewRepository placeShortReviewRepository; - private final UserRepository userRepository; + private final PlaceShortReviewRepository placeShortReviewRepository;; private final PlaceRepository placeRepository; + private final PlaceAnimationRepository placeAnimationRepository; @Override @Transactional - public PlaceShortReview createReview(Long placeId, PlaceShortReviewRequestDTO.CreateDTO request) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); - + public PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request) { Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - PlaceShortReview newReview = PlaceShortReviewConverter.toPlaceShortReview(request, user, place); + PlaceAnimation placeAnimation = placeAnimationRepository.findById(request.getPlaceAnimationId()) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); + + PlaceShortReview newReview = PlaceShortReviewConverter.toPlaceShortReview(request, user, place, placeAnimation); return placeShortReviewRepository.save(newReview); } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 81e3018e..adeca849 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -34,6 +34,7 @@ public enum ErrorStatus implements BaseErrorCode { // 명소 관련 에러 PLACE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4001", "존재하지 않는 명소입니다."), + PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "PLACE4002", "존재하지 않는 명소 애니메이션입니다."), // 이벤트 좋아요 관련 에러 EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), From d5aa9691b9497c69095c31d26b8902a575775340 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 27 Jan 2025 16:43:34 +0900 Subject: [PATCH 202/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C-=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_animation/repository/PlaceAnimationRepository.java | 3 +++ .../service/PlaceShortReviewCommandServiceImpl.java | 4 ++-- .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index dad57dba..d3f5def1 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -3,5 +3,8 @@ import com.otakumap.domain.mapping.PlaceAnimation; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface PlaceAnimationRepository extends JpaRepository { + Optional findByIdAndPlaceId(Long id, Long placeId); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index 5f1ec37a..f0b4250c 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -30,8 +30,8 @@ public PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRe Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - PlaceAnimation placeAnimation = placeAnimationRepository.findById(request.getPlaceAnimationId()) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); + PlaceAnimation placeAnimation = placeAnimationRepository.findByIdAndPlaceId(request.getPlaceAnimationId(), placeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.INVALID_PLACE_ANIMATION)); PlaceShortReview newReview = PlaceShortReviewConverter.toPlaceShortReview(request, user, place, placeAnimation); diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index adeca849..d35910dc 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -34,7 +34,7 @@ public enum ErrorStatus implements BaseErrorCode { // 명소 관련 에러 PLACE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4001", "존재하지 않는 명소입니다."), - PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "PLACE4002", "존재하지 않는 명소 애니메이션입니다."), + INVALID_PLACE_ANIMATION(HttpStatus.BAD_REQUEST, "PLACE4002", "해당 장소에 유효하지 않은 명소-애니메이션입니다."), // 이벤트 좋아요 관련 에러 EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), From 594c4f691f872535c28164ccf9686c7320e96546 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 27 Jan 2025 16:46:34 +0900 Subject: [PATCH 203/516] =?UTF-8?q?Refactor:=20operation=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 0ec19ef4..4b163dfe 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -42,7 +42,7 @@ public ApiResponse getPlace } @PostMapping("/places/{placeId}/short-review") - @Operation(summary = "특정 명소의 한 줄 리뷰 목록 작성 API", description = "특정 명소의 한 줄 리뷰를 작성하는 API입니다.") + @Operation(summary = "특정 명소의 한 줄 리뷰 목록 작성 API", description = "특정 명소의 한 줄 리뷰를 작성하는 API입니다. PlaceAnimationId는 특정 명소 관련 애니메이션 조회 API를 통해 얻을 수 있습니다.") public ApiResponse createReview( @CurrentUser User user, @PathVariable Long placeId, From b438bd5403ec712d94b93858402dd1ac85455f3a Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 28 Jan 2025 01:54:52 +0900 Subject: [PATCH 204/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../image/converter/ImageConverter.java | 8 +++ ...MultipartJackson2HttpMessageConverter.java | 30 ++++++++++ .../otakumap/domain/image/entity/Image.java | 2 +- .../image/repository/ImageRepository.java | 7 +++ .../image/service/ImageCommandService.java | 12 ++++ .../service/ImageCommandServiceImpl.java | 43 ++++++++++++++ .../image/service/ImageQueryService.java | 4 ++ .../image/service/ImageQueryServiceImpl.java | 4 ++ .../otakumap/global/config/AmazonConfig.java | 57 +++++++++++++++++++ .../otakumap/global/util/AmazonS3Util.java | 45 +++++++++++++++ 11 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/image/converter/MultipartJackson2HttpMessageConverter.java create mode 100644 src/main/java/com/otakumap/domain/image/repository/ImageRepository.java create mode 100644 src/main/java/com/otakumap/domain/image/service/ImageCommandService.java create mode 100644 src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/image/service/ImageQueryService.java create mode 100644 src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/config/AmazonConfig.java create mode 100644 src/main/java/com/otakumap/global/util/AmazonS3Util.java diff --git a/build.gradle b/build.gradle index 57ae7347..1b8bc561 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'com.google.code.gson:gson' + + // AWS S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java b/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java index be2ef4fa..7f2063a5 100644 --- a/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java +++ b/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java @@ -13,4 +13,12 @@ public static ImageResponseDTO.ImageDTO toImageDTO(Image image) { .fileUrl(image.getFileUrl()) .build(); } + + public static Image toImage(String uuid, String fileName, String fileUrl) { + return Image.builder() + .uuid(uuid) + .fileName(fileName) + .fileUrl(fileUrl) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/image/converter/MultipartJackson2HttpMessageConverter.java b/src/main/java/com/otakumap/domain/image/converter/MultipartJackson2HttpMessageConverter.java new file mode 100644 index 00000000..f7305262 --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/converter/MultipartJackson2HttpMessageConverter.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.image.converter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; + +@Component +public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { + public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java index d1cdf151..478715a9 100644 --- a/src/main/java/com/otakumap/domain/image/entity/Image.java +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -15,7 +15,7 @@ public class Image extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 100) + @Column(nullable = false, length = 100, unique = true) private String uuid; @Column(nullable = false, length = 100) diff --git a/src/main/java/com/otakumap/domain/image/repository/ImageRepository.java b/src/main/java/com/otakumap/domain/image/repository/ImageRepository.java new file mode 100644 index 00000000..6c06b9fe --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/repository/ImageRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.image.repository; + +import com.otakumap.domain.image.entity.Image; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ImageRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java new file mode 100644 index 00000000..a2f40a24 --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.image.service; + +import com.otakumap.domain.image.entity.Image; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface ImageCommandService { + Image uploadProfileImage(MultipartFile file, Long userId); + List uploadReviewImages(List files, Long reviewId); +} + diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java new file mode 100644 index 00000000..19189ee5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -0,0 +1,43 @@ +package com.otakumap.domain.image.service; + +import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.image.repository.ImageRepository; +import com.otakumap.global.util.AmazonS3Util; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ImageCommandServiceImpl implements ImageCommandService { + private final ImageRepository imageRepository; + private final AmazonS3Util amazonS3Util; + + @Override + @Transactional + public Image uploadProfileImage(MultipartFile file, Long userId) { + String keyName = amazonS3Util.generateProfileKeyName(); + String fileUrl = amazonS3Util.uploadFile(keyName, file); + + return imageRepository.save(ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl)); + } + + @Override + @Transactional + public List uploadReviewImages(List files, Long reviewId) { + return files.stream() + .map(file -> { + String keyName = amazonS3Util.generateReviewKeyName(); + String fileUrl = amazonS3Util.uploadFile(keyName, file); + + return ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); + }) + .map(imageRepository::save) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/otakumap/domain/image/service/ImageQueryService.java b/src/main/java/com/otakumap/domain/image/service/ImageQueryService.java new file mode 100644 index 00000000..273ebbee --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/service/ImageQueryService.java @@ -0,0 +1,4 @@ +package com.otakumap.domain.image.service; + +public interface ImageQueryService { +} diff --git a/src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java new file mode 100644 index 00000000..1d764d8c --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java @@ -0,0 +1,4 @@ +package com.otakumap.domain.image.service; + +public class ImageQueryServiceImpl { +} diff --git a/src/main/java/com/otakumap/global/config/AmazonConfig.java b/src/main/java/com/otakumap/global/config/AmazonConfig.java new file mode 100644 index 00000000..7fe2d4c1 --- /dev/null +++ b/src/main/java/com/otakumap/global/config/AmazonConfig.java @@ -0,0 +1,57 @@ +package com.otakumap.global.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Getter +public class AmazonConfig { + private AWSCredentials awsCredentials; + + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Value("${cloud.aws.s3.profilePath}") + private String profilePath; + + @Value("${cloud.aws.s3.reviewPath}") + private String reviewPath; + + @Value("${cloud.aws.s3.eventPath}") + private String eventPath; + + @PostConstruct + public void init() { this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey); } + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .withRegion(region) + .build(); + } + + @Bean + public AWSCredentialsProvider awsCredentialsProvider() { + return new AWSStaticCredentialsProvider(awsCredentials); + } +} diff --git a/src/main/java/com/otakumap/global/util/AmazonS3Util.java b/src/main/java/com/otakumap/global/util/AmazonS3Util.java new file mode 100644 index 00000000..45fa6eab --- /dev/null +++ b/src/main/java/com/otakumap/global/util/AmazonS3Util.java @@ -0,0 +1,45 @@ +package com.otakumap.global.util; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.otakumap.global.config.AmazonConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AmazonS3Util { + private final AmazonS3 amazonS3; + private final AmazonConfig amazonConfig; +// private final ImageR + + public String uploadFile(String keyName, MultipartFile file) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + + try { + amazonS3.putObject(amazonConfig.getBucket(), keyName, file.getInputStream(), metadata); + } catch (IOException e) { + log.error("error at AmazonS3Manager uploadFile : {}", (Object) e.getStackTrace()); + } + + return amazonS3.getUrl(amazonConfig.getBucket(), keyName).toString(); + } + + public String generateProfileKeyName() { + return amazonConfig.getProfilePath() + '/' + UuidGenerator.generateUuid(); + } + + public String generateReviewKeyName() { + return amazonConfig.getReviewPath() + '/' + UuidGenerator.generateUuid(); + } + + public String generateEventKeyName() { + return amazonConfig.getEventPath() + '/' + UuidGenerator.generateUuid(); + } +} \ No newline at end of file From 444b524072903192ea2cb935e25ff94099d9b97c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 28 Jan 2025 03:03:39 +0900 Subject: [PATCH 205/516] =?UTF-8?q?Feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 13 +++++++++++++ .../otakumap/domain/user/dto/UserRequestDTO.java | 6 ++++++ .../java/com/otakumap/domain/user/entity/User.java | 4 ++++ .../domain/user/service/UserCommandService.java | 2 ++ .../domain/user/service/UserCommandServiceImpl.java | 12 ++++++++++++ .../com/otakumap/global/config/AmazonConfig.java | 6 +++--- .../java/com/otakumap/global/util/AmazonS3Util.java | 7 ++++--- 7 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index 6416e899..387d1aca 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -14,10 +14,13 @@ 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 jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -86,4 +89,14 @@ public ApiResponse deleteAllReviews(@CurrentUser User user) { placeReviewCommandService.deleteAllByUserId(user.getId()); return ApiResponse.onSuccess("모든 후기가 성공적으로 삭제되었습니다."); } + + @PatchMapping(value = "/profile_image", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Operation(summary = "프로필 이미지 변경", description = "프로필 이미지를 변경합니다.") + public ApiResponse updateProfileImage( + @Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) + @CurrentUser User user, + @RequestPart("profileImage") MultipartFile profileImage) { + String profileImageUrl = userCommandService.updateProfileImage(user, profileImage); + return ApiResponse.onSuccess("프로필 이미지가 성공적으로 변경되었습니다. URL: " + profileImageUrl); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index fa62e827..cbae51c6 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -51,4 +51,10 @@ public static class ResetPasswordDTO { @Schema(description = "password", example = "otakumap1234!") String passwordCheck; } + + @Getter + public static class ProfileImageaUpdateDTO { + @NotBlank(message = "프로필 이미지를 선택해주세요.") + private String profileImageUrl; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 75645e22..b513024a 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -87,4 +87,8 @@ public void setNotification(Integer type, boolean isEnabled) { if (type == 1) { this.isCommunityActivityNotified = isEnabled; } else { this.isEventBenefitsNotified = isEnabled; } } + + public void setProflieImage(Image image) { + this.profileImage = image; + } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java index db0b006e..0cb717a9 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -2,10 +2,12 @@ import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.entity.User; +import org.springframework.web.multipart.MultipartFile; public interface UserCommandService { void updateNickname(User user, UserRequestDTO.UpdateNicknameDTO request); void reportEvent(UserRequestDTO.UserReportRequestDTO request); void updateNotificationSettings(User user, UserRequestDTO.NotificationSettingsRequestDTO request); void resetPassword(UserRequestDTO.ResetPasswordDTO request); + String updateProfileImage(User user, MultipartFile file); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 2e51d177..5e656294 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -1,5 +1,7 @@ package com.otakumap.domain.user.service; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.entity.User; @@ -12,6 +14,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; @Service @RequiredArgsConstructor @@ -19,6 +22,7 @@ public class UserCommandServiceImpl implements UserCommandService { private final UserRepository userRepository; private final EmailUtil emailUtil; private final PasswordEncoder passwordEncoder; + private final ImageCommandService imageCommandService; @Override @Transactional @@ -66,4 +70,12 @@ public void resetPassword(UserRequestDTO.ResetPasswordDTO request) { user.encodePassword(passwordEncoder.encode(request.getPassword())); userRepository.save(user); } + + @Override + public String updateProfileImage(User user, MultipartFile file) { + Image image = imageCommandService.uploadProfileImage(file, user.getId()); + user.setProflieImage(image); + userRepository.save(user); + return image.getFileUrl(); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/config/AmazonConfig.java b/src/main/java/com/otakumap/global/config/AmazonConfig.java index 7fe2d4c1..19d14996 100644 --- a/src/main/java/com/otakumap/global/config/AmazonConfig.java +++ b/src/main/java/com/otakumap/global/config/AmazonConfig.java @@ -29,13 +29,13 @@ public class AmazonConfig { @Value("${cloud.aws.s3.bucket}") private String bucket; - @Value("${cloud.aws.s3.profilePath}") + @Value("${cloud.aws.s3.path.profile}") private String profilePath; - @Value("${cloud.aws.s3.reviewPath}") + @Value("${cloud.aws.s3.path.review}") private String reviewPath; - @Value("${cloud.aws.s3.eventPath}") + @Value("${cloud.aws.s3.path.event}") private String eventPath; @PostConstruct diff --git a/src/main/java/com/otakumap/global/util/AmazonS3Util.java b/src/main/java/com/otakumap/global/util/AmazonS3Util.java index 45fa6eab..2e9721cb 100644 --- a/src/main/java/com/otakumap/global/util/AmazonS3Util.java +++ b/src/main/java/com/otakumap/global/util/AmazonS3Util.java @@ -9,6 +9,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.UUID; @Slf4j @Component @@ -32,14 +33,14 @@ public String uploadFile(String keyName, MultipartFile file) { } public String generateProfileKeyName() { - return amazonConfig.getProfilePath() + '/' + UuidGenerator.generateUuid(); + return amazonConfig.getProfilePath() + '/' + UUID.randomUUID().toString(); } public String generateReviewKeyName() { - return amazonConfig.getReviewPath() + '/' + UuidGenerator.generateUuid(); + return amazonConfig.getReviewPath() + '/' + UUID.randomUUID().toString(); } public String generateEventKeyName() { - return amazonConfig.getEventPath() + '/' + UuidGenerator.generateUuid(); + return amazonConfig.getEventPath() + '/' + UUID.randomUUID().toString(); } } \ No newline at end of file From b8697bec3764fcc8ac39dfc3de4f47206f35cc0d Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 28 Jan 2025 03:42:40 +0900 Subject: [PATCH 206/516] =?UTF-8?q?Refactor:=20User=20request=20DTO?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/user/dto/UserRequestDTO.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index cbae51c6..fa62e827 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -51,10 +51,4 @@ public static class ResetPasswordDTO { @Schema(description = "password", example = "otakumap1234!") String passwordCheck; } - - @Getter - public static class ProfileImageaUpdateDTO { - @NotBlank(message = "프로필 이미지를 선택해주세요.") - private String profileImageUrl; - } } \ No newline at end of file From 5abc2477227e36e5ddee2967dea73bf77045e59b Mon Sep 17 00:00:00 2001 From: haerxeong Date: Tue, 28 Jan 2025 04:45:03 +0900 Subject: [PATCH 207/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/contoller/ImageController.java | 33 +++++++++++++++++++ .../domain/image/dto/ImageRequestDTO.java | 12 +++++++ .../image/service/ImageCommandService.java | 1 + .../service/ImageCommandServiceImpl.java | 16 +++++++++ .../apiPayload/code/status/ErrorStatus.java | 5 ++- .../exception/handler/ImageHandler.java | 8 +++++ .../global/config/SecurityConfig.java | 3 +- .../otakumap/global/util/AmazonS3Util.java | 1 - 8 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/image/contoller/ImageController.java create mode 100644 src/main/java/com/otakumap/domain/image/dto/ImageRequestDTO.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/ImageHandler.java diff --git a/src/main/java/com/otakumap/domain/image/contoller/ImageController.java b/src/main/java/com/otakumap/domain/image/contoller/ImageController.java new file mode 100644 index 00000000..c21ff54f --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/contoller/ImageController.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.image.contoller; + +import com.otakumap.domain.image.dto.ImageRequestDTO; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.image.service.ImageCommandService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/images") +public class ImageController { + private final ImageCommandService imageCommandService; + + @PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Operation(summary = "이미지 업로드", description = "폴더를 선택하여 이미지를 업로드합니다.") + public ApiResponse uploadImage(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE), description = "폴더는 profile, review, event 중 하나를 선택해주세요.") + @RequestPart("folder") @Valid ImageRequestDTO.uploadDTO folder, + @RequestPart("image") MultipartFile image ) { + Image uploadedImage = imageCommandService.uploadaImage(image, folder.getFolder()); + return ApiResponse.onSuccess("이미지가 성공적으로 업로드되었습니다. URL: " + uploadedImage.getFileUrl()); + } +} diff --git a/src/main/java/com/otakumap/domain/image/dto/ImageRequestDTO.java b/src/main/java/com/otakumap/domain/image/dto/ImageRequestDTO.java new file mode 100644 index 00000000..fc35c70e --- /dev/null +++ b/src/main/java/com/otakumap/domain/image/dto/ImageRequestDTO.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.image.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +public class ImageRequestDTO { + @Getter + public static class uploadDTO { + @NotBlank + String folder; + } +} diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java index a2f40a24..8e9c65d9 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -8,5 +8,6 @@ public interface ImageCommandService { Image uploadProfileImage(MultipartFile file, Long userId); List uploadReviewImages(List files, Long reviewId); + Image uploadaImage(MultipartFile file, String folder); } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java index 19189ee5..fa6c1c52 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -3,6 +3,8 @@ import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.repository.ImageRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.ImageHandler; import com.otakumap.global.util.AmazonS3Util; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -40,4 +42,18 @@ public List uploadReviewImages(List files, Long reviewId) .map(imageRepository::save) .collect(Collectors.toList()); } + + @Override + @Transactional + public Image uploadaImage(MultipartFile file, String folder) { + String keyName = switch (folder) { + case "profile" -> amazonS3Util.generateProfileKeyName(); + case "review" -> amazonS3Util.generateReviewKeyName(); + case "event" -> amazonS3Util.generateEventKeyName(); + default -> throw new ImageHandler(ErrorStatus.INVALID_FOLDER); + }; + + String fileUrl = amazonS3Util.uploadFile(keyName, file); + return imageRepository.save(ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl)); + } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 80b0d76a..15448db2 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -67,7 +67,10 @@ public enum ErrorStatus implements BaseErrorCode { NOTIFICATION_ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "NOTIFICATION4004", "알림에 접근할 수 없습니다."), // 정렬 관련 에러 - INVALID_SORT_TYPE(HttpStatus.BAD_REQUEST, "SORT4001", "유효하지 않은 정렬 기준입니다."); + INVALID_SORT_TYPE(HttpStatus.BAD_REQUEST, "SORT4001", "유효하지 않은 정렬 기준입니다."), + + // 이미지 관련 에러 + INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/ImageHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/ImageHandler.java new file mode 100644 index 00000000..4839c951 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/ImageHandler.java @@ -0,0 +1,8 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class ImageHandler extends GeneralException { + public ImageHandler(BaseErrorCode errorCode) { super(errorCode); } +} diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 97cb0c57..c245add0 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -33,7 +33,8 @@ public class SecurityConfig { "/swagger-resources/**", "/v3/api-docs/**", "/api/auth/**", - "/api/users/reset-password/**" + "/api/users/reset-password/**", + "/api/images/**" }; @Bean diff --git a/src/main/java/com/otakumap/global/util/AmazonS3Util.java b/src/main/java/com/otakumap/global/util/AmazonS3Util.java index 2e9721cb..ea299b25 100644 --- a/src/main/java/com/otakumap/global/util/AmazonS3Util.java +++ b/src/main/java/com/otakumap/global/util/AmazonS3Util.java @@ -17,7 +17,6 @@ public class AmazonS3Util { private final AmazonS3 amazonS3; private final AmazonConfig amazonConfig; -// private final ImageR public String uploadFile(String keyName, MultipartFile file) { ObjectMetadata metadata = new ObjectMetadata(); From 74fd44d593d6236633b7f1e75b22c5155013989e Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 17:05:47 +0900 Subject: [PATCH 208/516] =?UTF-8?q?Feat:=20PlaceLocation=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20(?= =?UTF-8?q?=EC=9C=84=EB=8F=84,=20=EA=B2=BD=EB=8F=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceLocationConverter.java | 16 ++++++++++ .../dto/PlaceLocationResponse.java | 3 ++ .../place_location/entity/PlaceLocation.java | 31 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java create mode 100644 src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java create mode 100644 src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java diff --git a/src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java b/src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java new file mode 100644 index 00000000..7bcfd53e --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.place_location.converter; + +import com.otakumap.domain.eventLocation.entity.EventLocation; +import com.otakumap.domain.place_location.dto.PlaceLocationResponse; + +public class PlaceLocationConverter { + + public static PlaceLocationResponse toEventLocationDTO(EventLocation eventLocation) { + return new PlaceLocationResponse( + eventLocation.getId(), + eventLocation.getName(), + eventLocation.getLatitude(), + eventLocation.getLongitude() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java b/src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java new file mode 100644 index 00000000..618bd411 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java @@ -0,0 +1,3 @@ +package com.otakumap.domain.place_location.dto; + +public record PlaceLocationResponse(Long id, String name, String longitude, String latitude) { } diff --git a/src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java b/src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java new file mode 100644 index 00000000..f145c22e --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java @@ -0,0 +1,31 @@ +package com.otakumap.domain.place_location.entity; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceLocation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(columnDefinition = "VARCHAR(50)", nullable = false) + private String name; + + @Column(columnDefinition = "TEXT") + private String latitude; + + @Column(columnDefinition = "TEXT") + private String longitude; + + @OneToOne(mappedBy = "placeLocation", fetch = FetchType.LAZY) + private Place place; + +} From 3c7014fd47a2867ad994fd4a6e6a74f47805d44f Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 28 Jan 2025 17:21:56 +0900 Subject: [PATCH 209/516] =?UTF-8?q?Feat:=20=EC=A1=B0=ED=9A=8C=EC=88=98=20T?= =?UTF-8?q?op=207=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReviewSearchController.java | 3 +- .../reviews/converter/ReviewConverter.java | 13 ++++++- .../domain/reviews/dto/ReviewResponseDTO.java | 1 + .../repository/ReviewRepositoryCustom.java | 1 + .../repository/ReviewRepositoryImpl.java | 34 +++++++++++++++++++ .../reviews/service/ReviewQueryService.java | 1 + .../service/ReviewQueryServiceImpl.java | 5 +++ 7 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index d4842ef8..858ca905 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -27,7 +27,8 @@ public class ReviewSearchController { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), }) public ApiResponse getTop7ReviewList() { - return null; + ReviewResponseDTO.Top7ReviewPreViewListDTO top7Reviews = reviewQueryService.getTop7Reviews(); + return ApiResponse.onSuccess(top7Reviews); } @GetMapping("/reviews/search") diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 77127c66..22d9b004 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -7,12 +7,23 @@ public class ReviewConverter { - public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7ReviewPreViewDTO(EventReview eventReview) { + public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO(EventReview eventReview) { return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) .view(eventReview.getView()) + .type("event") + .build(); + } + + public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO(PlaceReview eventReview) { + return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() + .id(eventReview.getId()) + .title(eventReview.getTitle()) + .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .view(eventReview.getView()) + .type("place") .build(); } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index ab50a926..8c13f062 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -28,6 +28,7 @@ public static class Top7ReviewPreViewDTO { String title; ImageResponseDTO.ImageDTO reviewImage; Long view; + String type; } @Builder diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java index a24f3506..ac10ac18 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java @@ -5,4 +5,5 @@ public interface ReviewRepositoryCustom { Page getReviewsByKeyword(String keyword, int page, int size, String sort); + ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews(); } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index f1d13d97..797842c5 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -25,6 +25,8 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Repository @RequiredArgsConstructor @@ -109,4 +111,36 @@ private Page paginateReviews( return new PageImpl<>(reviews.subList(start, end), PageRequest.of(page, size), reviews.size()); } + + @Override + public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { + QPlaceReview placeReview = QPlaceReview.placeReview; + QEventReview eventReview = QEventReview.eventReview; + + List placeReviews = queryFactory.select(placeReview) + .from(placeReview) + .orderBy(placeReview.view.desc()) + .limit(7) + .fetch(); + List eventReviews = queryFactory.select(eventReview) + .from(eventReview) + .orderBy(eventReview.view.desc()) + .limit(7) + .fetch(); + + List top7Reviews = Stream.concat( + placeReviews.stream() + .map(ReviewConverter::toTop7PlaceReviewPreViewDTO), + eventReviews.stream() + .map(ReviewConverter::toTop7EventReviewPreViewDTO) + ) + .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView).reversed()) + .limit(7) + .collect(Collectors.toList()); + + + return ReviewResponseDTO.Top7ReviewPreViewListDTO.builder() + .reviews(top7Reviews) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index e29c966d..8fe56cb5 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -5,4 +5,5 @@ public interface ReviewQueryService { Page searchReviewsByKeyword(String keyword, int page, int size, String sort); + ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews(); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 24c019c4..e4cc4789 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -19,4 +19,9 @@ public Page searchReviewsByKeyword(S return reviewRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } + + @Override + public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { + return reviewRepositoryCustom.getTop7Reviews(); + } } From bae00dd5c2a7228ff05564564e6b4d2263809708 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 17:39:39 +0900 Subject: [PATCH 210/516] =?UTF-8?q?Feat:=20Place=EC=97=90=20=EC=9E=88?= =?UTF-8?q?=EB=8D=98=20=EC=9C=84=EB=8F=84,=20=EA=B2=BD=EB=8F=84=EB=A5=BC?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=ED=95=98=EA=B3=A0=20PlaceLocation?= =?UTF-8?q?=EC=97=90=20=EC=97=B0=EA=B2=B0=ED=95=98=EC=97=AC=20=EC=9C=84?= =?UTF-8?q?=EB=8F=84,=20=EA=B2=BD=EB=8F=84=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/place/entity/Place.java | 11 +++++------ .../place_like/converter/PlaceLikeConverter.java | 4 ++-- .../domain/place_like/dto/PlaceLikeResponseDTO.java | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index e6ae8e5a..d333a6b4 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place.entity; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place_location.entity.PlaceLocation; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -26,12 +27,6 @@ public class Place extends BaseEntity { @Column(nullable = false, length = 20) private String name; - @Column(nullable = false) - private Double lat; - - @Column(nullable = false) - private Double lng; - @Column(name = "detail", nullable = false, length = 100) private String detail; @@ -46,4 +41,8 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_location_id", referencedColumnName = "id", nullable = false) + private PlaceLocation placeLocation; } diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index c90506f4..2efc6b92 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -14,8 +14,8 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(Place .placeId(placeLike.getPlace().getId()) .name(placeLike.getPlace().getName()) .detail(placeLike.getPlace().getDetail()) - .lat(placeLike.getPlace().getLat()) - .lng(placeLike.getPlace().getLng()) + .lat(placeLike.getPlace().getPlaceLocation().getLatitude()) + .lng(placeLike.getPlace().getPlaceLocation().getLongitude()) .savedAt(placeLike.getPlace().getSavedAt()) .isFavorite(placeLike.getIsFavorite()) .build(); diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index c504544b..05ec8571 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -18,8 +18,8 @@ public static class PlaceLikePreViewDTO { Long placeId; String name; String detail; - Double lat; - Double lng; + String lat; + String lng; LocalDateTime savedAt; Boolean isFavorite; } From f90058cf489cc24f92648423c5f4d73869a3544c Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 17:57:12 +0900 Subject: [PATCH 211/516] =?UTF-8?q?Feat:=20savedAt=20=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C(=ED=98=84=EC=9E=AC=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EA=B8=B0=EB=8A=A5=EC=97=90=EC=84=9C=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=EC=97=86=EC=9D=8C),=20=EB=82=98=EC=A4=91=EC=97=90=20?= =?UTF-8?q?=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=A0=20=EB=95=8C=20bookmark=EB=A1=9C=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/place/entity/Place.java | 2 -- .../domain/place_like/converter/PlaceLikeConverter.java | 1 - .../otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java | 1 - 3 files changed, 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index d333a6b4..dec62fc1 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -30,8 +30,6 @@ public class Place extends BaseEntity { @Column(name = "detail", nullable = false, length = 100) private String detail; - private LocalDateTime savedAt; - @Column(name = "is_favorite", nullable = false) @ColumnDefault("false") private Boolean isFavorite; diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 2efc6b92..3e0a9416 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -16,7 +16,6 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(Place .detail(placeLike.getPlace().getDetail()) .lat(placeLike.getPlace().getPlaceLocation().getLatitude()) .lng(placeLike.getPlace().getPlaceLocation().getLongitude()) - .savedAt(placeLike.getPlace().getSavedAt()) .isFavorite(placeLike.getIsFavorite()) .build(); diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 05ec8571..592dc78c 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -20,7 +20,6 @@ public static class PlaceLikePreViewDTO { String detail; String lat; String lng; - LocalDateTime savedAt; Boolean isFavorite; } From 3078a8e5ab882878e78b2ac2e6d10357d9184930 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 18:09:07 +0900 Subject: [PATCH 212/516] =?UTF-8?q?Feat:=20@CurrentUser=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=8F=20'=EC=9E=A5=EC=86=8C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94'=20=EA=B4=80=EB=A0=A8=20url=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceLikeController.java | 14 ++- .../service/PlaceLikeQueryService.java | 3 +- .../service/PlaceLikeQueryServiceImpl.java | 6 +- .../place_like/PlaceLikeControllerTest.java | 86 ------------------- 4 files changed, 10 insertions(+), 99 deletions(-) delete mode 100644 src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 35966edf..e800a43c 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -17,27 +17,25 @@ import java.util.List; @RestController -@RequestMapping("/api") +@RequestMapping("/api/place-likes") @RequiredArgsConstructor @Validated public class PlaceLikeController { private final PlaceLikeQueryService placeLikeQueryService; private final PlaceLikeCommandService placeLikeCommandService; - // 로그인 시 엔드포인트 변경 예정 @Operation(summary = "저장된 장소 목록 조회", description = "저장된 장소 목록을 불러옵니다.") - @GetMapping( "/users/{userId}/saved-places") + @GetMapping( "") @Parameters({ - @Parameter(name = "userId", description = "사용자 ID"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") }) - public ApiResponse getPlaceLikeList(@PathVariable Long userId, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { - return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(userId, lastId, limit)); + public ApiResponse getPlaceLikeList(@CurrentUser User user, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(user, lastId, limit)); } @Operation(summary = "저장된 장소 삭제", description = "저장된 장소를 삭제합니다.") - @DeleteMapping("/saved-places") + @DeleteMapping("") @Parameters({ @Parameter(name = "placeIds", description = "저장된 장소 id List"), }) @@ -47,7 +45,7 @@ public ApiResponse deletePlaceLike(@RequestParam(required = false) @Exis } @Operation(summary = "장소 저장", description = "장소를 저장합니다.") - @PostMapping("/places/{placeId}") + @PostMapping("/{placeId}") @Parameters({ @Parameter(name = "placeId", description = "장소 Id") }) diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java index c8c9a601..5a7b39b5 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java @@ -1,8 +1,9 @@ package com.otakumap.domain.place_like.service; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.user.entity.User; public interface PlaceLikeQueryService { - PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(Long userId, Long lastId, int limit); + PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit); boolean isPlaceLikeExist(Long id); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index 8e0a9027..8ceddc9c 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -28,14 +28,12 @@ public class PlaceLikeQueryServiceImpl implements PlaceLikeQueryService { private final UserRepository userRepository; @Override - public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(Long userId, Long lastId, int limit) { + public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit) { List result; Pageable pageable = PageRequest.of(0, limit+1); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); - Page placeLikePage = placeLikeRepository.findByUserIdAndIdLessThanOrderByIdDesc(userId, lastId, pageable); + Page placeLikePage = placeLikeRepository.findByUserIdAndIdLessThanOrderByIdDesc(user.getId(), lastId, pageable); if (lastId.equals(0L)) { // lastId가 0일 경우: 처음부터 데이터를 조회 diff --git a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java b/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java deleted file mode 100644 index 145c4dc3..00000000 --- a/src/test/java/com/otakumap/place_like/PlaceLikeControllerTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.otakumap.place_like; - -import com.otakumap.domain.place_like.controller.PlaceLikeController; -import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; -import com.otakumap.domain.place_like.service.PlaceLikeCommandService; -import com.otakumap.domain.place_like.service.PlaceLikeQueryService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import java.util.Arrays; -import java.util.List; - -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; - -@ExtendWith(MockitoExtension.class) -public class PlaceLikeControllerTest { - - @Mock - private PlaceLikeQueryService placeLikeQueryService; - - @Mock - private PlaceLikeCommandService placeLikeCommandService; - - @InjectMocks - private PlaceLikeController placeLikeController; - - private MockMvc mockMvc; - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(placeLikeController).build(); - } - - @Test - void testGetPlaceLikeList() throws Exception { - Long userId = 1L; - Long lastId = 0L; - int limit = 10; - - // PlaceLikePreViewDTO 객체 생성 - PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike1 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(1L, 1L, 101L, true); - PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLike2 = new PlaceLikeResponseDTO.PlaceLikePreViewDTO(2L, 1L, 102L, false); - - // PlaceLikePreViewListDTO 객체 생성 - List placeLikes = Arrays.asList(placeLike1, placeLike2); - PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikeListDTO = new PlaceLikeResponseDTO.PlaceLikePreViewListDTO(placeLikes, true, 2L); - - when(placeLikeQueryService.getPlaceLikeList(userId, lastId, limit)).thenReturn(placeLikeListDTO); - - mockMvc.perform(get("/api/users/1/saved-places") - .param("lastId", "0") - .param("limit", "10") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.result.placeLikes[0].id").value(1)) - .andExpect(jsonPath("$.result.placeLikes[0].userId").value(1)) - .andExpect(jsonPath("$.result.placeLikes[0].placeId").value(101)) - .andExpect(jsonPath("$.result.placeLikes[0].isFavorite").value(true)); - - } - - @Test - void testDeletePlaceLike() throws Exception { - List placeIds = Arrays.asList(1L, 2L); - - // 삭제 동작 모의 - mockMvc.perform(delete("/api/saved-places") - .param("placeIds", "1,2") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - // "성공입니다."라는 메시지가 반환되면 이를 검증 - .andExpect(jsonPath("$.message").value("성공입니다.")); - } - -} From 01a0701543fc55d5b61c8e774f5d8fa256c26ab2 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 18:10:07 +0900 Subject: [PATCH 213/516] =?UTF-8?q?Chore:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java | 1 - .../domain/place_like/service/PlaceLikeQueryServiceImpl.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 592dc78c..f99d733a 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -5,7 +5,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; import java.util.List; public class PlaceLikeResponseDTO { diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index 8ceddc9c..a7513750 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -1,6 +1,5 @@ package com.otakumap.domain.place_like.service; -import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.place_like.converter.PlaceLikeConverter; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; @@ -9,7 +8,6 @@ import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; From 61175a3821719000feb9d504cd7663fd814534b4 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 18:10:18 +0900 Subject: [PATCH 214/516] =?UTF-8?q?Chore:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/place/entity/Place.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index dec62fc1..95993551 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -8,7 +8,6 @@ import lombok.*; import org.hibernate.annotations.ColumnDefault; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; From 3b4e24907bdc6ae3d486840a329e607e7938ffd5 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 28 Jan 2025 19:43:09 +0900 Subject: [PATCH 215/516] =?UTF-8?q?Feat:=20=ED=95=84=EB=93=9C=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_like/controller/EventLikeController.java | 6 +++--- .../event_like/converter/EventLikeConverter.java | 6 +++--- .../domain/event_like/dto/EventLikeRequestDTO.java | 6 +++--- .../domain/event_like/dto/EventLikeResponseDTO.java | 4 ++-- .../otakumap/domain/event_like/entity/EventLike.java | 8 ++++---- .../event_like/repository/EventLikeRepository.java | 11 +---------- .../event_like/service/EventLikeCommandService.java | 2 +- .../service/EventLikeCommandServiceImpl.java | 4 ++-- .../event_like/service/EventLikeQueryService.java | 2 +- .../event_like/service/EventLikeQueryServiceImpl.java | 7 +++---- 10 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 9c5a6e18..19dfd3fc 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -60,8 +60,8 @@ public ApiResponse deleteEventLike(@RequestParam(required = false) @Exis } @Operation(summary = "저장된 이벤트 즐겨찾기/즐겨찾기 취소", description = "저장된 이벤트를 즐겨찾기 또는 취소합니다.") - @PatchMapping("/{eventLikeId}/bookmark") - public ApiResponse bookmarkEventLike(@PathVariable Long eventLikeId, @RequestBody @Valid EventLikeRequestDTO.BookmarkDTO request) { - return ApiResponse.onSuccess(EventLikeConverter.toBookmarkResultDTO(eventLikeCommandService.bookmarkEventLike(eventLikeId, request))); + @PatchMapping("/{eventLikeId}/favorites") + public ApiResponse bookmarkEventLike(@PathVariable Long eventLikeId, @RequestBody @Valid EventLikeRequestDTO.FavoriteDTO request) { + return ApiResponse.onSuccess(EventLikeConverter.toBookmarkResultDTO(eventLikeCommandService.favoriteEventLike(eventLikeId, request))); } } diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index 8d10cf4e..736b62ab 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -17,7 +17,7 @@ public static EventLikeResponseDTO.EventLikePreViewDTO eventLikePreViewDTO(Event .thumbnail(eventLike.getEvent().getThumbnailImage().getFileUrl()) .startDate(eventLike.getEvent().getStartDate()) .endDate(eventLike.getEvent().getEndDate()) - .isBookmarked(eventLike.getIsBookmarked()) + .isFavorite(eventLike.getIsFavorite()) .eventType(eventLike.getEvent().getType()) .build(); @@ -34,14 +34,14 @@ public static EventLike toEventLike(User user, Event event) { return EventLike.builder() .event(event) .user(user) - .isBookmarked(false) + .isFavorite(false) .build(); } public static EventLikeResponseDTO.BookmarkResultDTO toBookmarkResultDTO(EventLike eventLike) { return EventLikeResponseDTO.BookmarkResultDTO.builder() .eventLikeId(eventLike.getId()) - .isBookmarked(eventLike.getIsBookmarked()) + .isFavorite(eventLike.getIsFavorite()) .build(); } } diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java index cdde86c5..0ed34779 100644 --- a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java @@ -6,8 +6,8 @@ public class EventLikeRequestDTO { @Getter - public static class BookmarkDTO { - @NotNull(message = "북마크 여부 입력은 필수입니다.") - Boolean isBookmarked; + public static class FavoriteDTO { + @NotNull(message = "즐겨찾기 여부 입력은 필수입니다.") + Boolean isFavorite; } } diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java index 0677cb7f..348929fb 100644 --- a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java @@ -21,7 +21,7 @@ public static class EventLikePreViewDTO { String thumbnail; LocalDate startDate; LocalDate endDate; - boolean isBookmarked; + Boolean isFavorite; EventType eventType; } @@ -41,6 +41,6 @@ public static class EventLikePreViewListDTO { @AllArgsConstructor public static class BookmarkResultDTO { Long eventLikeId; - Boolean isBookmarked; + Boolean isFavorite; } } diff --git a/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java b/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java index a78f1af2..23b0a76b 100644 --- a/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java +++ b/src/main/java/com/otakumap/domain/event_like/entity/EventLike.java @@ -29,11 +29,11 @@ public class EventLike extends BaseEntity { @JoinColumn(name = "event_id", nullable = false) private Event event; - @Column(name = "is_bookmarked", nullable = false) + @Column(name = "is_favorite", nullable = false) @ColumnDefault("false") - private Boolean isBookmarked; + private Boolean isFavorite; - public void setIsBookmarked(boolean isBookmarked) { - this.isBookmarked = isBookmarked; + public void setIsFavorite(boolean isFavorite) { + this.isFavorite = isFavorite; } } diff --git a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java index 9efed967..bff93808 100644 --- a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java +++ b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java @@ -1,17 +1,8 @@ package com.otakumap.domain.event_like.repository; -import com.otakumap.domain.event.entity.enums.EventType; -import com.otakumap.domain.event_like.entity.EventLike; -import com.otakumap.domain.user.entity.User; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; +import com.otakumap.domain.event_like.entity.EventLike;; import org.springframework.data.jpa.repository.JpaRepository; -import java.time.LocalDateTime; public interface EventLikeRepository extends JpaRepository { - Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); - Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); - Page findAllByUserIsAndEventTypeOrderByCreatedAtDesc(User user, EventType type, Pageable pageable); - Page findAllByUserIsAndEventTypeAndCreatedAtLessThanOrderByCreatedAtDesc(User user, EventType type, LocalDateTime createdAt, Pageable pageable); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java index 34335794..d22e1b6e 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java @@ -9,5 +9,5 @@ public interface EventLikeCommandService { void addEventLike(User user, Long eventId); void deleteEventLike(List eventIds); - EventLike bookmarkEventLike(Long eventLikeId, EventLikeRequestDTO.BookmarkDTO request); + EventLike favoriteEventLike(Long eventLikeId, EventLikeRequestDTO.FavoriteDTO request); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index 00561367..4e73736b 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -39,9 +39,9 @@ public void deleteEventLike(List eventIds) { } @Override - public EventLike bookmarkEventLike(Long eventLikeId, EventLikeRequestDTO.BookmarkDTO request) { + public EventLike favoriteEventLike(Long eventLikeId, EventLikeRequestDTO.FavoriteDTO request) { EventLike eventLike = eventLikeRepository.findById(eventLikeId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_LIKE_NOT_FOUND)); - eventLike.setIsBookmarked(request.getIsBookmarked()); + eventLike.setIsFavorite(request.getIsFavorite()); return eventLikeRepository.save(eventLike); } } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java index 4d38ba75..fcb3c242 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryService.java @@ -4,6 +4,6 @@ import com.otakumap.domain.user.entity.User; public interface EventLikeQueryService { - EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Boolean isBookmarked, Long lastId, int limit); + EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Boolean isFavorite, Long lastId, int limit); boolean isEventLikeExist(Long id); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java index 03d24375..a10f33a1 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java @@ -24,7 +24,7 @@ public class EventLikeQueryServiceImpl implements EventLikeQueryService { private final JPAQueryFactory jpaQueryFactory; @Override - public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Boolean isBookmarked, Long lastId, int limit) { + public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, Integer type, Boolean isFavorite, Long lastId, int limit) { EventType eventType = (type == null || type == 0) ? null : EventType.values()[type - 1]; QEventLike qEventLike = QEventLike.eventLike; @@ -36,8 +36,8 @@ public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, predicate.and(qEventLike.event.type.eq(eventType)); } - if (isBookmarked != null) { - predicate.and(qEventLike.isBookmarked.eq(isBookmarked)); + if (isFavorite != null) { + predicate.and(qEventLike .isFavorite.eq(isFavorite)); } if (lastId != null && lastId > 0) { @@ -56,7 +56,6 @@ public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, return createEventLikePreviewListDTO(result, limit); } - private EventLikeResponseDTO.EventLikePreViewListDTO createEventLikePreviewListDTO(List eventLikes, int limit) { boolean hasNext = eventLikes.size() > limit; Long lastId = null; From 322e482f78a6e1e2aca268fbbb467208f6dc7e99 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 28 Jan 2025 22:59:22 +0900 Subject: [PATCH 216/516] =?UTF-8?q?Chore:=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/controller/PlaceLikeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index e800a43c..7f80021f 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -25,7 +25,7 @@ public class PlaceLikeController { private final PlaceLikeCommandService placeLikeCommandService; @Operation(summary = "저장된 장소 목록 조회", description = "저장된 장소 목록을 불러옵니다.") - @GetMapping( "") + @GetMapping("") @Parameters({ @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") From 3a33bd00189874d5457cc5f0ad24795c689559c9 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 29 Jan 2025 00:15:31 +0900 Subject: [PATCH 217/516] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=20=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/service/EventLikeCommandServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index 4e73736b..bd85078d 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -4,7 +4,6 @@ import com.otakumap.domain.event.repository.EventRepository; import com.otakumap.domain.event_like.converter.EventLikeConverter; import com.otakumap.domain.event_like.dto.EventLikeRequestDTO; -import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.user.entity.User; From afbd68f79b8db2de5221e8a2d5ba3f413e9cb0a8 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 29 Jan 2025 00:17:17 +0900 Subject: [PATCH 218/516] =?UTF-8?q?Refactor:=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EA=B4=80=EB=A0=A8=20=EC=9D=B4=EB=A6=84=20'Favorite?= =?UTF-8?q?'=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_like/controller/EventLikeController.java | 10 +++++----- .../event_like/converter/EventLikeConverter.java | 4 ++-- .../domain/event_like/dto/EventLikeResponseDTO.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index 19dfd3fc..c141b648 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -41,12 +41,12 @@ public ApiResponse postPlaceLike(@CurrentUser User user, @PathVariable L @GetMapping( "") @Parameters({ @Parameter(name = "type", description = "이벤트 타입 -> 1: 팝업 스토어, 2: 전시회, 3: 콜라보 카페"), - @Parameter(name = "isBookmarked", description = "북마크 여부(필수 X) -> true: 북마크 목록 조회"), + @Parameter(name = "isFavorite", description = "즐겨찾기 여부(필수 X) -> true: 즐겨찾기 목록 조회"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") }) - public ApiResponse getEventLikeList(@CurrentUser User user, @RequestParam(required = false) Integer type, @RequestParam(required = false) Boolean isBookmarked, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { - return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(user, type, isBookmarked, lastId, limit)); + public ApiResponse getEventLikeList(@CurrentUser User user, @RequestParam(required = false) Integer type, @RequestParam(required = false) Boolean isFavorite, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(eventLikeQueryService.getEventLikeList(user, type, isFavorite, lastId, limit)); } @Operation(summary = "저장된 이벤트 삭제(이벤트 찜하기 취소)", description = "저장된 이벤트를 삭제합니다.") @@ -61,7 +61,7 @@ public ApiResponse deleteEventLike(@RequestParam(required = false) @Exis @Operation(summary = "저장된 이벤트 즐겨찾기/즐겨찾기 취소", description = "저장된 이벤트를 즐겨찾기 또는 취소합니다.") @PatchMapping("/{eventLikeId}/favorites") - public ApiResponse bookmarkEventLike(@PathVariable Long eventLikeId, @RequestBody @Valid EventLikeRequestDTO.FavoriteDTO request) { - return ApiResponse.onSuccess(EventLikeConverter.toBookmarkResultDTO(eventLikeCommandService.favoriteEventLike(eventLikeId, request))); + public ApiResponse favoriteEventLike(@PathVariable Long eventLikeId, @RequestBody @Valid EventLikeRequestDTO.FavoriteDTO request) { + return ApiResponse.onSuccess(EventLikeConverter.toFavoriteResultDTO(eventLikeCommandService.favoriteEventLike(eventLikeId, request))); } } diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index 736b62ab..10eba513 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -38,8 +38,8 @@ public static EventLike toEventLike(User user, Event event) { .build(); } - public static EventLikeResponseDTO.BookmarkResultDTO toBookmarkResultDTO(EventLike eventLike) { - return EventLikeResponseDTO.BookmarkResultDTO.builder() + public static EventLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(EventLike eventLike) { + return EventLikeResponseDTO.FavoriteResultDTO.builder() .eventLikeId(eventLike.getId()) .isFavorite(eventLike.getIsFavorite()) .build(); diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java index 348929fb..634f1275 100644 --- a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeResponseDTO.java @@ -39,7 +39,7 @@ public static class EventLikePreViewListDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public static class BookmarkResultDTO { + public static class FavoriteResultDTO { Long eventLikeId; Boolean isFavorite; } From fb0fd8af55e1e33d9a14926b8aed0068be1d4920 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 29 Jan 2025 00:17:34 +0900 Subject: [PATCH 219/516] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=20=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java index 0ed34779..4fb36ef8 100644 --- a/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_like/dto/EventLikeRequestDTO.java @@ -1,6 +1,5 @@ package com.otakumap.domain.event_like.dto; -import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotNull; import lombok.Getter; From bc00b6869392325630ca5e5ab1f3633b1b8d5ec3 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 29 Jan 2025 00:18:06 +0900 Subject: [PATCH 220/516] =?UTF-8?q?Feat:=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=97=AC=EB=B6=80=EA=B0=80=20True=EC=9D=BC=20?= =?UTF-8?q?=EB=95=8C=EB=A7=8C=20=EA=B2=80=EC=83=89=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/service/EventLikeQueryServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java index a10f33a1..26b3d9da 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeQueryServiceImpl.java @@ -36,7 +36,8 @@ public EventLikeResponseDTO.EventLikePreViewListDTO getEventLikeList(User user, predicate.and(qEventLike.event.type.eq(eventType)); } - if (isFavorite != null) { + // isFavorite이 true일 때만 검색 조건에 추가 + if (isFavorite != null && isFavorite) { predicate.and(qEventLike .isFavorite.eq(isFavorite)); } From 3e380c4228b038a8eeba83733b5c1b6d7b59cc88 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 00:24:01 +0900 Subject: [PATCH 221/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=A0=9C=EB=AA=A9=EC=9D=84=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B8=B0=20=EB=95=8C?= =?UTF-8?q?=EB=AC=B8=EC=97=90=20RouteLike=EC=97=90=20name=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route_like/entity/RouteLike.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index f7f53fdb..d244b51d 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -23,6 +23,9 @@ public class RouteLike extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(length = 50, nullable = false) + private String name; + @ManyToOne @JoinColumn(name = "user_id", nullable = false) private User user; From ab630ff983be3b09559d8cc19adc18ab3f195bf7 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 00:25:35 +0900 Subject: [PATCH 222/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=A0=9C=EB=AA=A9=EC=9D=84=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B8=B0=20=EB=95=8C?= =?UTF-8?q?=EB=AC=B8=EC=97=90=20RouteLike=EC=97=90=20name=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/route_like/converter/RouteLikeConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index c97b01fa..149117d2 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -8,6 +8,7 @@ public class RouteLikeConverter { public static RouteLike toRouteLike(User user, Route route) { return RouteLike.builder() + .name(route.getName()) .user(user) .route(route) .isFavorite(Boolean.TRUE) From bfadec00d2c9840633c080735bb63403e69bc029 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 00:58:35 +0900 Subject: [PATCH 223/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=A0=9C=EB=AA=A9=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/route/entity/Route.java | 1 + .../controller/RouteLikeController.java | 12 ++++++++++++ .../converter/RouteLikeConverter.java | 2 +- .../route_like/dto/UpdateNameRequestDTO.java | 19 +++++++++++++++++++ .../domain/route_like/entity/RouteLike.java | 4 ++++ .../repository/RouteLikeRepository.java | 5 +++++ .../service/RouteLikeCommandService.java | 1 + .../service/RouteLikeCommandServiceImpl.java | 18 +++++++++++++++++- 8 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java index 18278ebf..784477d8 100644 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -27,4 +27,5 @@ public class Route extends BaseEntity { @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) private List routeItems; + } diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 74e84b75..21b32f9e 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -1,6 +1,7 @@ package com.otakumap.domain.route_like.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.route_like.dto.UpdateNameRequestDTO; import com.otakumap.domain.route_like.service.RouteLikeCommandService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; @@ -44,4 +45,15 @@ public ApiResponse deleteSavedRoute(@RequestParam(required = false) @Exi return ApiResponse.onSuccess("저장된 루트가 성공적으로 삭제되었습니다."); } + @Operation(summary = "루트 제목 편집", description = "루트의 제목을 편집(수정)합니다.") + @PatchMapping("/routes/{routeId}/name") + @Parameters({ + @Parameter(name = "routeId", description = "루트 Id") + }) + public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @RequestBody UpdateNameRequestDTO request) { + routeLikeCommandService.updateName(routeId, request.getName()); + + return ApiResponse.onSuccess("루트 제목이 성공적으로 수정되었습니다."); + } + } diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index 149117d2..77b01e67 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -11,7 +11,7 @@ public static RouteLike toRouteLike(User user, Route route) { .name(route.getName()) .user(user) .route(route) - .isFavorite(Boolean.TRUE) + .isFavorite(Boolean.FALSE) .build(); } } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java new file mode 100644 index 00000000..013e07f5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java @@ -0,0 +1,19 @@ +package com.otakumap.domain.route_like.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class UpdateNameRequestDTO { + + @NotBlank(message = "제목은 빈칸이 불가합니다.") + @Size(max = 50, message = "제목은 50자 이하여야 합니다.") + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index d244b51d..f52bc8b9 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -34,7 +34,11 @@ public class RouteLike extends BaseEntity { @JoinColumn(name = "route_id") private Route route; + // 즐겨찾기 여부 @Column(name = "is_favorite", nullable = false) @ColumnDefault("false") private Boolean isFavorite; + + public void setName(String name) { + } } diff --git a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java index 1bdff80d..666076f9 100644 --- a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java +++ b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java @@ -5,6 +5,11 @@ import com.otakumap.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface RouteLikeRepository extends JpaRepository { boolean existsByUserAndRoute(User user, Route route); + + // route_id로 RouteLike 조회 + Optional findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java index 235b5f6a..366fcef5 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -9,4 +9,5 @@ public interface RouteLikeCommandService { void saveRouteLike(User user, Long routeId); void deleteRouteLike(List routeIds); + void updateName(Long routeId, String name); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 3d432e33..d5f7dfd7 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -27,9 +27,11 @@ public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { public void saveRouteLike(User user, Long routeId) { Route route = routeRepository.findById(routeId) .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); - if(routeLikeRepository.existsByUserAndRoute(user, route)) { + + if (routeLikeRepository.existsByUserAndRoute(user, route)) { throw new RouteHandler(ErrorStatus.ROUTE_LIKE_ALREADY_EXISTS); } + RouteLike routeLike = RouteLikeConverter.toRouteLike(user, route); routeLikeRepository.save(routeLike); } @@ -41,4 +43,18 @@ public void deleteRouteLike(List routeIds) { entityManager.flush(); entityManager.clear(); } + + @Override + public void updateName(Long routeId, String name) { + + // 루트가 저장되어있는지 확인 + RouteLike routeLike = routeLikeRepository.findByRouteId(routeId) + .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); + + // 이름 변경 + routeLike.setName(name); + + // 변경된 엔티티 저장 + routeLikeRepository.save(routeLike); + } } From 08fb53761e9e19667f5a03d8d7b45da3347f4314 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 01:12:35 +0900 Subject: [PATCH 224/516] =?UTF-8?q?Fix:=20@Setter=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=A8=EC=9C=BC=EB=A1=9C=EC=8D=A8=20=EC=A0=9C=EB=AA=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=95=88=20=EB=90=98=EB=8D=98=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EA=B3=A0=EC=B9=A8=20(=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EB=B0=A9=EB=B2=95=EC=9C=BC=EB=A1=9C=EB=8A=94,=20setName=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=EC=97=90=20this.name=20=3D=20name;?= =?UTF-8?q?=20=EB=84=A3=EC=96=B4=EC=A3=BC=EC=96=B4=EC=95=BC=20=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route_like/entity/RouteLike.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index f52bc8b9..19b9b1d1 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -23,6 +23,7 @@ public class RouteLike extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Setter @Column(length = 50, nullable = false) private String name; @@ -39,6 +40,4 @@ public class RouteLike extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; - public void setName(String name) { - } } From 45a057a254acc184a01caf1f4420dcce02e6a819 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 16:59:54 +0900 Subject: [PATCH 225/516] =?UTF-8?q?Fix:=20@Setter=20=EC=A7=80=EC=96=91,=20?= =?UTF-8?q?setName=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route_like/entity/RouteLike.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index 19b9b1d1..5ff32269 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -23,7 +23,6 @@ public class RouteLike extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Setter @Column(length = 50, nullable = false) private String name; @@ -40,4 +39,7 @@ public class RouteLike extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; + public void setName(String name) { + this.name = name; + } } From d329a52dbad3da3a3f1b171540b97f2c8ac02090 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 17:06:01 +0900 Subject: [PATCH 226/516] =?UTF-8?q?Refactor:=20dto=EB=A5=BC=20record=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteLikeController.java | 2 +- .../route_like/dto/UpdateNameRequestDTO.java | 20 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 21b32f9e..5af855e7 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -51,7 +51,7 @@ public ApiResponse deleteSavedRoute(@RequestParam(required = false) @Exi @Parameter(name = "routeId", description = "루트 Id") }) public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @RequestBody UpdateNameRequestDTO request) { - routeLikeCommandService.updateName(routeId, request.getName()); + routeLikeCommandService.updateName(routeId, request.name()); return ApiResponse.onSuccess("루트 제목이 성공적으로 수정되었습니다."); } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java index 013e07f5..ea97ecd9 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java @@ -2,18 +2,10 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import lombok.Getter; -public class UpdateNameRequestDTO { - - @NotBlank(message = "제목은 빈칸이 불가합니다.") - @Size(max = 50, message = "제목은 50자 이하여야 합니다.") - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} +public record UpdateNameRequestDTO( + @NotBlank(message = "제목은 빈칸이 불가합니다.") + @Size(max = 50, message = "제목은 50자 이하여야 합니다.") + String name +) {} From 4540155b0894ea50976477867c513bffbd6e00c6 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 29 Jan 2025 17:06:23 +0900 Subject: [PATCH 227/516] =?UTF-8?q?Feat:=20=EC=A7=84=ED=96=89=20=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EC=9D=B8=EA=B8=B0=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 10 ++++++ .../event/converter/EventConverter.java | 10 ++++++ .../domain/event/dto/EventResponseDTO.java | 12 +++++++ .../repository/EventRepositoryCustom.java | 8 +++++ .../event/repository/EventRepositoryImpl.java | 36 +++++++++++++++++++ .../event/service/EventCustomService.java | 9 +++++ .../event/service/EventCustomServiceImpl.java | 20 +++++++++++ .../event/service/EventQueryServiceImpl.java | 2 -- 8 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java create mode 100644 src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java create mode 100644 src/main/java/com/otakumap/domain/event/service/EventCustomService.java create mode 100644 src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 8bf189ca..1e784b8c 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -1,6 +1,7 @@ package com.otakumap.domain.event.controller; import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.service.EventCustomService; import com.otakumap.domain.event.service.EventQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -12,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequestMapping("/api") @RequiredArgsConstructor @@ -19,6 +22,13 @@ public class EventController { private final EventQueryService eventQueryService; + private final EventCustomService eventCustomService; + + @Operation(summary = "진행 중인 인기 이벤트 조회", description = "진행 중인 인기 이벤트의 목록(8개)를 불러옵니다.") + @GetMapping("/events/popular") + public ApiResponse> getEventDetail() { + return ApiResponse.onSuccess(eventCustomService.getPopularEvents()); + } @Operation(summary = "이벤트 상세 정보 조회", description = "특정 이벤트의 상세 정보를 불러옵니다.") @GetMapping("/events/{eventId}/details") diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index ac292223..07a7bc49 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -7,6 +7,16 @@ public class EventConverter { + public static EventResponseDTO.EventDTO toEventDTO(Event event) { + return EventResponseDTO.EventDTO.builder() + .id(event.getId()) + .title(event.getTitle()) + .thumbnail(ImageConverter.toImageDTO(event.getThumbnailImage())) + .startDate(event.getStartDate()) + .endDate(event.getEndDate()) + .build(); + } + public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { return EventResponseDTO.EventDetailDTO.builder() .id(event.getId()) diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index 4f381b75..75bb285d 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -11,6 +11,18 @@ public class EventResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventDTO { + Long id; + String title; + LocalDate startDate; + LocalDate endDate; + ImageResponseDTO.ImageDTO thumbnail; + } + @Builder @Getter @NoArgsConstructor diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java new file mode 100644 index 00000000..0ed4c0ee --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.event.repository; + +import com.otakumap.domain.event.dto.EventResponseDTO; +import java.util.List; + +public interface EventRepositoryCustom { + List getPopularEvents(); +} diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java new file mode 100644 index 00000000..6efd3343 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -0,0 +1,36 @@ +package com.otakumap.domain.event.repository; + +import com.otakumap.domain.event.converter.EventConverter; +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.QEvent; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class EventRepositoryImpl implements EventRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List getPopularEvents() { + QEvent event = QEvent.event; + + List events = queryFactory.selectFrom(event) + .where(event.endDate.goe(LocalDate.now()) + .and(event.startDate.loe(LocalDate.now()))) + .orderBy(Expressions.numberTemplate(Double.class, "function('rand')").asc()) + .limit(8) + .fetch(); + + return events.stream() + .map(EventConverter::toEventDTO) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java new file mode 100644 index 00000000..6641a314 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.event.service; + +import com.otakumap.domain.event.dto.EventResponseDTO; + +import java.util.List; + +public interface EventCustomService { + List getPopularEvents(); +} diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java new file mode 100644 index 00000000..d1ad9060 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.event.service; + +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.repository.EventRepositoryCustom; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EventCustomServiceImpl implements EventCustomService { + private final EventRepositoryCustom eventRepository; + + @Override + public List getPopularEvents() { + return eventRepository.getPopularEvents(); + } +} diff --git a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java index 2a3afe2e..d58970b2 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java @@ -10,8 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - - @Service @RequiredArgsConstructor @Transactional(readOnly = true) From d24590b517ec7a0783965e6d7eaada558334d0df Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 17:19:07 +0900 Subject: [PATCH 228/516] =?UTF-8?q?Feat:=20place=5Flocation=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=9C=84?= =?UTF-8?q?=EB=8F=84,=20=EA=B2=BD=EB=8F=84=20=EC=B9=BC=EB=9F=BC=20place?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/entity/Place.java | 11 ++++--- .../converter/PlaceLikeConverter.java | 4 +-- .../place_like/dto/PlaceLikeResponseDTO.java | 4 +-- .../converter/PlaceLocationConverter.java | 16 ---------- .../dto/PlaceLocationResponse.java | 3 -- .../place_location/entity/PlaceLocation.java | 31 ------------------- 6 files changed, 10 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java delete mode 100644 src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java delete mode 100644 src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 74a05a18..26d365a8 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,7 +1,6 @@ package com.otakumap.domain.place.entity; import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.place_location.entity.PlaceLocation; import com.otakumap.domain.mapping.PlaceHashTag; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; @@ -27,6 +26,12 @@ public class Place extends BaseEntity { @Column(nullable = false, length = 20) private String name; + @Column(nullable = false) + private Double lat; + + @Column(nullable = false) + private Double lng; + @Column(name = "detail", nullable = false, length = 100) private String detail; @@ -40,10 +45,6 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_location_id", referencedColumnName = "id", nullable = false) - private PlaceLocation placeLocation; - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeHashTagList = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 3e0a9416..1a3b2764 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -14,8 +14,8 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(Place .placeId(placeLike.getPlace().getId()) .name(placeLike.getPlace().getName()) .detail(placeLike.getPlace().getDetail()) - .lat(placeLike.getPlace().getPlaceLocation().getLatitude()) - .lng(placeLike.getPlace().getPlaceLocation().getLongitude()) + .lat(placeLike.getPlace().getLat()) + .lng(placeLike.getPlace().getLng()) .isFavorite(placeLike.getIsFavorite()) .build(); diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index f99d733a..2469fc7f 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -17,8 +17,8 @@ public static class PlaceLikePreViewDTO { Long placeId; String name; String detail; - String lat; - String lng; + Double lat; + Double lng; Boolean isFavorite; } diff --git a/src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java b/src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java deleted file mode 100644 index 7bcfd53e..00000000 --- a/src/main/java/com/otakumap/domain/place_location/converter/PlaceLocationConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.otakumap.domain.place_location.converter; - -import com.otakumap.domain.eventLocation.entity.EventLocation; -import com.otakumap.domain.place_location.dto.PlaceLocationResponse; - -public class PlaceLocationConverter { - - public static PlaceLocationResponse toEventLocationDTO(EventLocation eventLocation) { - return new PlaceLocationResponse( - eventLocation.getId(), - eventLocation.getName(), - eventLocation.getLatitude(), - eventLocation.getLongitude() - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java b/src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java deleted file mode 100644 index 618bd411..00000000 --- a/src/main/java/com/otakumap/domain/place_location/dto/PlaceLocationResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.otakumap.domain.place_location.dto; - -public record PlaceLocationResponse(Long id, String name, String longitude, String latitude) { } diff --git a/src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java b/src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java deleted file mode 100644 index f145c22e..00000000 --- a/src/main/java/com/otakumap/domain/place_location/entity/PlaceLocation.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.otakumap.domain.place_location.entity; - -import com.otakumap.domain.place.entity.Place; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class PlaceLocation extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(columnDefinition = "VARCHAR(50)", nullable = false) - private String name; - - @Column(columnDefinition = "TEXT") - private String latitude; - - @Column(columnDefinition = "TEXT") - private String longitude; - - @OneToOne(mappedBy = "placeLocation", fetch = FetchType.LAZY) - private Place place; - -} From c9ba51d4dea255924902f51ab185d175a6251ae8 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 17:20:28 +0900 Subject: [PATCH 229/516] =?UTF-8?q?Chore:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java index ea97ecd9..e152be6d 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/UpdateNameRequestDTO.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import lombok.Getter; public record UpdateNameRequestDTO( @NotBlank(message = "제목은 빈칸이 불가합니다.") From e222e76aaedada0ced6694922e10fb77aa3aed0a Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 29 Jan 2025 17:31:38 +0900 Subject: [PATCH 230/516] =?UTF-8?q?Feat:=20DTO=EC=97=90=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20validation=EC=9D=84=20=EC=A0=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=81=EC=9A=A9=EC=8B=9C=ED=82=A4=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20@Valid=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/controller/RouteLikeController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 5af855e7..c857d609 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -50,7 +51,7 @@ public ApiResponse deleteSavedRoute(@RequestParam(required = false) @Exi @Parameters({ @Parameter(name = "routeId", description = "루트 Id") }) - public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @RequestBody UpdateNameRequestDTO request) { + public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @RequestBody @Valid UpdateNameRequestDTO request) { routeLikeCommandService.updateName(routeId, request.name()); return ApiResponse.onSuccess("루트 제목이 성공적으로 수정되었습니다."); From 8b1fb36c51c84eb2b50f51cf9a1edacc8d6a6439 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 29 Jan 2025 18:58:44 +0900 Subject: [PATCH 231/516] =?UTF-8?q?Fix:=20=ED=8C=8C=EC=9D=BC=20=EC=9C=A0?= =?UTF-8?q?=ED=98=95=20=EC=A0=80=EC=9E=A5=20=EC=95=88=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/util/AmazonS3Util.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/global/util/AmazonS3Util.java b/src/main/java/com/otakumap/global/util/AmazonS3Util.java index ea299b25..764d45c2 100644 --- a/src/main/java/com/otakumap/global/util/AmazonS3Util.java +++ b/src/main/java/com/otakumap/global/util/AmazonS3Util.java @@ -21,6 +21,7 @@ public class AmazonS3Util { public String uploadFile(String keyName, MultipartFile file) { ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); try { amazonS3.putObject(amazonConfig.getBucket(), keyName, file.getInputStream(), metadata); From 69b989e4219bc88053faf5bc3c4fef1d5bfeda58 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 29 Jan 2025 19:42:26 +0900 Subject: [PATCH 232/516] =?UTF-8?q?Refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/image/service/ImageQueryService.java | 4 ---- .../otakumap/domain/image/service/ImageQueryServiceImpl.java | 4 ---- 2 files changed, 8 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/image/service/ImageQueryService.java delete mode 100644 src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/image/service/ImageQueryService.java b/src/main/java/com/otakumap/domain/image/service/ImageQueryService.java deleted file mode 100644 index 273ebbee..00000000 --- a/src/main/java/com/otakumap/domain/image/service/ImageQueryService.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.otakumap.domain.image.service; - -public interface ImageQueryService { -} diff --git a/src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java deleted file mode 100644 index 1d764d8c..00000000 --- a/src/main/java/com/otakumap/domain/image/service/ImageQueryServiceImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.otakumap.domain.image.service; - -public class ImageQueryServiceImpl { -} From 085d1f337ef7e218d4e653f477e722c229f67503 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 29 Jan 2025 19:47:39 +0900 Subject: [PATCH 233/516] =?UTF-8?q?Fix:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B6=8C=ED=95=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/SecurityConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index c245add0..0fe4c2f9 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -34,7 +34,6 @@ public class SecurityConfig { "/v3/api-docs/**", "/api/auth/**", "/api/users/reset-password/**", - "/api/images/**" }; @Bean From cd238594b8b1db6cbbe561413806e57e98e813c2 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 29 Jan 2025 20:37:33 +0900 Subject: [PATCH 234/516] =?UTF-8?q?Refactor:=20=EC=89=BC=ED=91=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 0fe4c2f9..97cb0c57 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -33,7 +33,7 @@ public class SecurityConfig { "/swagger-resources/**", "/v3/api-docs/**", "/api/auth/**", - "/api/users/reset-password/**", + "/api/users/reset-password/**" }; @Bean From 29e75aa894392e6229b5e3f0eb6bd749a5e54b28 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 02:21:08 +0900 Subject: [PATCH 235/516] =?UTF-8?q?Rename:=20eventLocation=20->=20event=5F?= =?UTF-8?q?location,=20eventShortReview=20->=20event=5Fshort=5Freview=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/converter/EventConverter.java | 2 +- .../domain/event/dto/EventResponseDTO.java | 2 +- .../com/otakumap/domain/event/entity/Event.java | 2 +- .../converter/EventLocationConverter.java | 6 +++--- .../dto/EventLocationResponseDTO.java | 2 +- .../entity/EventLocation.java | 2 +- .../controller/EventShortReviewController.java | 14 +++++++------- .../converter/EventShortReviewConverter.java | 8 ++++---- .../dto/EventShortReviewRequestDTO.java | 2 +- .../dto/EventShortReviewResponseDTO.java | 2 +- .../entity/EventShortReview.java | 2 +- .../repository/EventShortReviewRepository.java | 4 ++-- .../service/EventShortReviewCommandService.java | 6 +++--- .../EventShortReviewCommandServiceImpl.java | 10 +++++----- 14 files changed, 32 insertions(+), 32 deletions(-) rename src/main/java/com/otakumap/domain/{eventLocation => event_location}/converter/EventLocationConverter.java (70%) rename src/main/java/com/otakumap/domain/{eventLocation => event_location}/dto/EventLocationResponseDTO.java (88%) rename src/main/java/com/otakumap/domain/{eventLocation => event_location}/entity/EventLocation.java (93%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/controller/EventShortReviewController.java (79%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/converter/EventShortReviewConverter.java (89%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/dto/EventShortReviewRequestDTO.java (83%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/dto/EventShortReviewResponseDTO.java (95%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/entity/EventShortReview.java (94%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/repository/EventShortReviewRepository.java (74%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/service/EventShortReviewCommandService.java (59%) rename src/main/java/com/otakumap/domain/{eventShortReview => event_short_review}/service/EventShortReviewCommandServiceImpl.java (83%) diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index ac292223..820f62be 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -2,7 +2,7 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.eventLocation.converter.EventLocationConverter; +import com.otakumap.domain.event_location.converter.EventLocationConverter; import com.otakumap.domain.image.converter.ImageConverter; public class EventConverter { diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index 4f381b75..e68a0438 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -1,6 +1,6 @@ package com.otakumap.domain.event.dto; -import com.otakumap.domain.eventLocation.dto.EventLocationResponseDTO; +import com.otakumap.domain.event_location.dto.EventLocationResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index ebe0c7a8..d294e063 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -3,7 +3,7 @@ import com.otakumap.domain.event.entity.enums.EventStatus; import com.otakumap.domain.event.entity.enums.EventType; import com.otakumap.domain.event.entity.enums.Genre; -import com.otakumap.domain.eventLocation.entity.EventLocation; +import com.otakumap.domain.event_location.entity.EventLocation; import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; diff --git a/src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java b/src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java similarity index 70% rename from src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java rename to src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java index b086abe8..f73baa61 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/converter/EventLocationConverter.java +++ b/src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java @@ -1,7 +1,7 @@ -package com.otakumap.domain.eventLocation.converter; +package com.otakumap.domain.event_location.converter; -import com.otakumap.domain.eventLocation.dto.EventLocationResponseDTO; -import com.otakumap.domain.eventLocation.entity.EventLocation; +import com.otakumap.domain.event_location.dto.EventLocationResponseDTO; +import com.otakumap.domain.event_location.entity.EventLocation; public class EventLocationConverter { diff --git a/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java b/src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java similarity index 88% rename from src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java rename to src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java index ae177d6a..c6c48514 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/dto/EventLocationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.eventLocation.dto; +package com.otakumap.domain.event_location.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java b/src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java similarity index 93% rename from src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java rename to src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java index 0d6bc35e..30a7dd28 100644 --- a/src/main/java/com/otakumap/domain/eventLocation/entity/EventLocation.java +++ b/src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.eventLocation.entity; +package com.otakumap.domain.event_location.entity; import com.otakumap.domain.event.entity.Event; import com.otakumap.global.common.BaseEntity; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java similarity index 79% rename from src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java rename to src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java index 8a4293f3..d33c45de 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java @@ -1,11 +1,11 @@ -package com.otakumap.domain.eventShortReview.controller; +package com.otakumap.domain.event_short_review.controller; -import com.otakumap.domain.eventShortReview.converter.EventShortReviewConverter; -import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; -import com.otakumap.domain.eventShortReview.dto.EventShortReviewResponseDTO; -import com.otakumap.domain.eventShortReview.entity.EventShortReview; -import com.otakumap.domain.eventShortReview.repository.EventShortReviewRepository; -import com.otakumap.domain.eventShortReview.service.EventShortReviewCommandService; +import com.otakumap.domain.event_short_review.converter.EventShortReviewConverter; +import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.event_short_review.dto.EventShortReviewResponseDTO; +import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.event_short_review.repository.EventShortReviewRepository; +import com.otakumap.domain.event_short_review.service.EventShortReviewCommandService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java b/src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java similarity index 89% rename from src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java rename to src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java index 86ad2c36..68fa54c4 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/converter/EventShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java @@ -1,10 +1,10 @@ -package com.otakumap.domain.eventShortReview.converter; +package com.otakumap.domain.event_short_review.converter; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; -import com.otakumap.domain.eventShortReview.dto.EventShortReviewResponseDTO; -import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.event_short_review.dto.EventShortReviewResponseDTO; +import com.otakumap.domain.event_short_review.entity.EventShortReview; import com.otakumap.domain.image.converter.ImageConverter; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java similarity index 83% rename from src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java rename to src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java index 939ea944..2fab4b36 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.eventShortReview.dto; +package com.otakumap.domain.event_short_review.dto; import lombok.Getter; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java similarity index 95% rename from src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java rename to src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java index d2bf4af8..53bedeb1 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/dto/EventShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.eventShortReview.dto; +package com.otakumap.domain.event_short_review.dto; import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java similarity index 94% rename from src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java rename to src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java index 50f9a56c..1ff7d94c 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/entity/EventShortReview.java +++ b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.eventShortReview.entity; +package com.otakumap.domain.event_short_review.entity; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.event.entity.Event; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java b/src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java similarity index 74% rename from src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java rename to src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java index 437abe24..21adf7ac 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/repository/EventShortReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java @@ -1,7 +1,7 @@ -package com.otakumap.domain.eventShortReview.repository; +package com.otakumap.domain.event_short_review.repository; import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.event_short_review.entity.EventShortReview; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java similarity index 59% rename from src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java rename to src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java index e16ba531..4885ac6c 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java @@ -1,7 +1,7 @@ -package com.otakumap.domain.eventShortReview.service; +package com.otakumap.domain.event_short_review.service; -import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; -import com.otakumap.domain.eventShortReview.entity.EventShortReview; +import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.event_short_review.entity.EventShortReview; import org.springframework.data.domain.Page; public interface EventShortReviewCommandService { diff --git a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java similarity index 83% rename from src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java rename to src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java index f88121e7..b5914c38 100644 --- a/src/main/java/com/otakumap/domain/eventShortReview/service/EventShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java @@ -1,13 +1,13 @@ -package com.otakumap.domain.eventShortReview.service; +package com.otakumap.domain.event_short_review.service; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; -import com.otakumap.domain.eventShortReview.converter.EventShortReviewConverter; -import com.otakumap.domain.eventShortReview.dto.EventShortReviewRequestDTO; -import com.otakumap.domain.eventShortReview.entity.EventShortReview; -import com.otakumap.domain.eventShortReview.repository.EventShortReviewRepository; +import com.otakumap.domain.event_short_review.converter.EventShortReviewConverter; +import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; +import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.event_short_review.repository.EventShortReviewRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; From 0af38d43dce747824c70db99419db32d435d2b33 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 02:34:02 +0900 Subject: [PATCH 236/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=ED=9B=84=EA=B8=B0=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventShortReviewController.java | 5 ++++- .../dto/EventShortReviewRequestDTO.java | 2 -- .../service/EventShortReviewCommandService.java | 3 ++- .../service/EventShortReviewCommandServiceImpl.java | 10 +++------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java index d33c45de..b8ea08e8 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java @@ -1,11 +1,13 @@ package com.otakumap.domain.event_short_review.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.event_short_review.converter.EventShortReviewConverter; import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; import com.otakumap.domain.event_short_review.dto.EventShortReviewResponseDTO; import com.otakumap.domain.event_short_review.entity.EventShortReview; import com.otakumap.domain.event_short_review.repository.EventShortReviewRepository; import com.otakumap.domain.event_short_review.service.EventShortReviewCommandService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -29,8 +31,9 @@ public class EventShortReviewController { @Parameter(name = "eventId", description = "특정 이벤트의 Id") }) public ApiResponse createEventShortReview(@PathVariable Long eventId, + @CurrentUser User user, @RequestBody EventShortReviewRequestDTO.NewEventShortReviewDTO request) { - EventShortReview eventShortReview = eventShortReviewCommandService.createEventShortReview(eventId, request); + EventShortReview eventShortReview = eventShortReviewCommandService.createEventShortReview(eventId, user, request); return ApiResponse.onSuccess(EventShortReviewConverter.toNewEventShortReviewDTO(eventShortReview)); } diff --git a/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java index 2fab4b36..50448ff6 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java @@ -6,9 +6,7 @@ public class EventShortReviewRequestDTO { @Getter public static class NewEventShortReviewDTO { - Long userId; // 로그인 구현 전까지 임의로 request body로 받음 Float rating; String content; - } } diff --git a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java index 4885ac6c..d2fcebee 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java @@ -2,9 +2,10 @@ import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; public interface EventShortReviewCommandService { - EventShortReview createEventShortReview(Long eventId, EventShortReviewRequestDTO.NewEventShortReviewDTO request); + EventShortReview createEventShortReview(Long eventId, User user, EventShortReviewRequestDTO.NewEventShortReviewDTO request); Page getEventShortReviewsByEventId(Long eventId, Integer page); } diff --git a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java index b5914c38..8fb99755 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java @@ -1,16 +1,15 @@ package com.otakumap.domain.event_short_review.service; -import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; import com.otakumap.domain.event_short_review.converter.EventShortReviewConverter; import com.otakumap.domain.event_short_review.dto.EventShortReviewRequestDTO; import com.otakumap.domain.event_short_review.entity.EventShortReview; import com.otakumap.domain.event_short_review.repository.EventShortReviewRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -27,13 +26,10 @@ public class EventShortReviewCommandServiceImpl implements EventShortReviewComma @Override @Transactional - public EventShortReview createEventShortReview(Long eventId, EventShortReviewRequestDTO.NewEventShortReviewDTO request) { + public EventShortReview createEventShortReview(Long eventId, User user, EventShortReviewRequestDTO.NewEventShortReviewDTO request) { Event event = eventRepository.findById(eventId) .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); - EventShortReview eventShortReview = EventShortReviewConverter.toEventShortReview(request, event, user); return eventShortReviewRepository.save(eventShortReview); From c4f561363dacdbebac0059fcabbd848eb55c89c4 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 04:13:23 +0900 Subject: [PATCH 237/516] =?UTF-8?q?Feat:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/entity/Animation.java | 11 ++++++++++ .../event_review/entity/EventReview.java | 22 ++++++++++++++++--- .../otakumap/domain/image/entity/Image.java | 10 +++++++++ .../place_review/entity/PlaceReview.java | 22 ++++++++++++++++--- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index 1259b0c9..32398b4b 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -1,9 +1,14 @@ package com.otakumap.domain.animation.entity; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @@ -17,4 +22,10 @@ public class Animation extends BaseEntity { @Column(nullable = false, length = 20) private String name; + + @OneToMany(mappedBy = "animation") + private List eventReviews = new ArrayList<>(); + + @OneToMany(mappedBy = "animation") + private List placeReviews = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 2ed19347..0144a9b8 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -1,7 +1,9 @@ package com.otakumap.domain.event_review.entity; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -9,6 +11,9 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @DynamicUpdate @@ -33,9 +38,12 @@ public class EventReview extends BaseEntity { @Column(nullable = false) private Float rating; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "image_id", referencedColumnName = "id") - private Image image; +// @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) +// @JoinColumn(name = "image_id", referencedColumnName = "id") +// private Image image; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") + private List images = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") @@ -44,4 +52,12 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "animation_id") + private Animation animation; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", referencedColumnName = "id") + private Route route; } diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java index d1cdf151..4cf08e16 100644 --- a/src/main/java/com/otakumap/domain/image/entity/Image.java +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -1,5 +1,7 @@ package com.otakumap.domain.image.entity; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -23,4 +25,12 @@ public class Image extends BaseEntity { @Column(nullable = false, length = 300) private String fileUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_review_id") + private PlaceReview placeReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_review_id") + private EventReview eventReview; } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 99cc122d..9202fa27 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -1,8 +1,10 @@ package com.otakumap.domain.place_review.entity; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -10,6 +12,9 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @DynamicInsert @@ -31,9 +36,12 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "image_id", referencedColumnName = "id") - private Image image; +// @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) +// @JoinColumn(name = "image_id", referencedColumnName = "id") +// private Image image; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview") + private List images = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @@ -46,4 +54,12 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; // 리뷰와 PlaceAnimation 연결 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "animation_id") + private Animation animation; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", referencedColumnName = "id") + private Route route; } From 4a453798318b886b87c378b73f5a494a8510a15b Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 06:55:03 +0900 Subject: [PATCH 238/516] =?UTF-8?q?Feat:=20Controller=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReviewSearchController.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index d4842ef8..915d88aa 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -9,10 +9,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -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.*; @RestController @RequiredArgsConstructor @@ -50,4 +47,18 @@ public ApiResponse> getSearched return ApiResponse.onSuccess(searchResults); } + + @GetMapping("/reviews/{reviewId}") + @Operation(summary = "특정 여행 후기 조회", description = "특정 여행 후기를 상세 페이지에서 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "reviewId", description = "이벤트 or 명소의 후기 id 입니다."), + @Parameter(name = "type", description = "리뷰의 종류를 특정합니다. 'event' 또는 'place' 여야 합니다.") + }) + public ApiResponse getReviewDetail(@PathVariable Long reviewId, @RequestParam(defaultValue = "place") String type) { + + return ApiResponse.onSuccess(reviewQueryService.getReviewDetail(reviewId, type)); + } } From e718d101a9f892141c7480e12e4b51326c4f36ed Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 06:57:08 +0900 Subject: [PATCH 239/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20DTO=20=EB=B0=8F=20Converter=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route/converter/RouteConverter.java | 21 +++++++++++++++++++ .../domain/route/dto/RouteResponseDTO.java | 21 +++++++++++++++++++ .../converter/RouteItemConverter.java | 16 ++++++++++++++ .../route_item/dto/RouteItemResponseDTO.java | 21 +++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/route/converter/RouteConverter.java create mode 100644 src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java create mode 100644 src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java new file mode 100644 index 00000000..b0a65a3e --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.route.converter; + +import com.otakumap.domain.route.dto.RouteResponseDTO; +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_item.converter.RouteItemConverter; + +public class RouteConverter { + + public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { + + if(route == null) { + return new RouteResponseDTO.RouteDTO(); + } + + return RouteResponseDTO.RouteDTO.builder() + .routeId(route.getId()) + .routeItems(route.getRouteItems().stream() + .map(RouteItemConverter::toRouteItemDTO).toList()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java new file mode 100644 index 00000000..c86489eb --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.route.dto; + +import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class RouteResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RouteDTO { + Long routeId; + List routeItems; + } +} diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java new file mode 100644 index 00000000..5f097adc --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.route_item.converter; + +import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; +import com.otakumap.domain.route_item.entity.RouteItem; + +public class RouteItemConverter { + + public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { + return RouteItemResponseDTO.RouteItemDTO.builder() + .routeItemId(routeItem.getId()) + .itemId(routeItem.getItemId()) + .itemType(routeItem.getItemType()) + .itemOrder(routeItem.getItemOrder()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java new file mode 100644 index 00000000..1fb1b466 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.route_item.dto; + +import com.otakumap.domain.route_item.enums.ItemType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class RouteItemResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RouteItemDTO { + Long routeItemId; + Long itemId; + ItemType itemType; + Integer itemOrder; + } +} From e3e9b4ce54e2aed450527c6daf958ec073b3c569 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:01:41 +0900 Subject: [PATCH 240/516] =?UTF-8?q?Feat:=20=EC=97=AC=ED=96=89=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EA=B4=80=EB=A0=A8=20DTO,=20Converter=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/converter/ReviewConverter.java | 45 +++++++++++++++++-- .../domain/reviews/dto/ReviewResponseDTO.java | 20 +++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 77127c66..30b3b22a 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -4,6 +4,9 @@ import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.route.converter.RouteConverter; + +import java.util.Objects; public class ReviewConverter { @@ -11,7 +14,7 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7ReviewPreViewDTO(Even return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .reviewImage(ImageConverter.toImageDTO(eventReview.getImages().get(0))) // 나중에 수정 .view(eventReview.getView()) .build(); } @@ -22,7 +25,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr .id(eventReview.getEvent().getId()) .title(eventReview.getTitle()) .content(eventReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .reviewImage(ImageConverter.toImageDTO(eventReview.getImages().get(0))) // 나중에 수정 .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") @@ -35,10 +38,46 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .id(placeReview.getPlace().getId()) .title(placeReview.getTitle()) .content(placeReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(placeReview.getImage())) + .reviewImage(ImageConverter.toImageDTO(placeReview.getImages().get(0))) // 나중에 수정 .view(placeReview.getView()) .createdAt(placeReview.getCreatedAt()) .type("place") .build(); } + + public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview) { + return ReviewResponseDTO.ReviewDetailDTO.builder() + .reviewId(placeReview.getId()) + .animationName(placeReview.getAnimation() != null ? placeReview.getAnimation().getName() : null) + .title(placeReview.getTitle()) + .view(placeReview.getView()) + .content(placeReview.getContent()) + .reviewImages(placeReview.getImages().stream() + .filter(Objects::nonNull) + .map(ImageConverter::toImageDTO) + .toList()) + .userName(placeReview.getUser().getName()) + .profileImage(ImageConverter.toImageDTO(placeReview.getUser().getProfileImage())) + .createdAt(placeReview.getCreatedAt()) + .route(RouteConverter.toRouteDTO(placeReview.getRoute())) + .build(); + } + + public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview) { + return ReviewResponseDTO.ReviewDetailDTO.builder() + .reviewId(eventReview.getId()) + .animationName(eventReview.getAnimation() != null ? eventReview.getAnimation().getName() : null) + .title(eventReview.getTitle()) + .view(eventReview.getView()) + .content(eventReview.getContent()) + .reviewImages(eventReview.getImages().stream() + .filter(Objects::nonNull) + .map(ImageConverter::toImageDTO) + .toList()) + .userName(eventReview.getUser().getName()) + .profileImage(ImageConverter.toImageDTO(eventReview.getUser().getProfileImage())) + .createdAt(eventReview.getCreatedAt()) + .route(RouteConverter.toRouteDTO(eventReview.getRoute())) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index ab50a926..84c71396 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -1,6 +1,7 @@ package com.otakumap.domain.reviews.dto; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.route.dto.RouteResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -44,4 +45,23 @@ public static class SearchedReviewPreViewDTO { LocalDateTime createdAt; ImageResponseDTO.ImageDTO reviewImage; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewDetailDTO { + Long reviewId; + String animationName; + String title; + Long view; + String content; + List reviewImages; + + String userName; + ImageResponseDTO.ImageDTO profileImage; + LocalDateTime createdAt; + + RouteResponseDTO.RouteDTO route; + } } From f613e05f9a1a0c124023e7b3c2589ba19b84f447 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:03:24 +0900 Subject: [PATCH 241/516] =?UTF-8?q?Feat:=20ReviewQueryService=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/service/ReviewQueryService.java | 2 ++ .../service/ReviewQueryServiceImpl.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index e29c966d..e246aba9 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -5,4 +5,6 @@ public interface ReviewQueryService { Page searchReviewsByKeyword(String keyword, int page, int size, String sort); + + ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, String type); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 24c019c4..85398e16 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -1,7 +1,15 @@ package com.otakumap.domain.reviews.service; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -13,10 +21,28 @@ public class ReviewQueryServiceImpl implements ReviewQueryService { private final ReviewRepositoryCustom reviewRepositoryCustom; + private final EventReviewRepository eventReviewRepository; + private final PlaceReviewRepository placeReviewRepository; @Override public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { return reviewRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } + + @Override + public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, String type) { + + if(type.equals("event")) { + EventReview eventReview = eventReviewRepository.findById(reviewId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); + + return ReviewConverter.toEventReviewDetailDTO(eventReview); + } else { + PlaceReview placeReview = placeReviewRepository.findById(reviewId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); + + return ReviewConverter.toPlaceReviewDetailDTO(placeReview); + } + } } From c3e0f24051ca4785e65b1354d589d23099c2b3c1 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:03:51 +0900 Subject: [PATCH 242/516] =?UTF-8?q?Feat:=20ErrorStatus=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/apiPayload/code/status/ErrorStatus.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 80b0d76a..6c42274c 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -36,19 +36,26 @@ public enum ErrorStatus implements BaseErrorCode { PLACE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4001", "존재하지 않는 명소입니다."), INVALID_PLACE_ANIMATION(HttpStatus.BAD_REQUEST, "PLACE4002", "해당 장소에 유효하지 않은 명소-애니메이션입니다."), + // 명소 좋아요 관련 에러 + PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4003", "저장되지 않은 명소입니다."), + + // 명소 후기 관련 에러 + PLACE_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4004", "존재하지 않는 명소 후기입니다."), + + // 이벤트 좋아요 관련 에러 EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), // 이벤트 상세 정보 관련 에러 EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."), - // 명소 좋아요 관련 에러 - PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."), + // 이벤트 후기 관련 에러 + EVENT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4003", "존재하지 않는 이벤트 후기입니다."), + // 후기 검색 관련 에러 REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), - // 애니메이션 관련 에러 ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4001", "존재하지 않는 애니메이션입니다"), PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4002", "존재하지 않는 애니메이션입니다"), From ef226e5768b696c7d649adeeee05c69f8d7c207a Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:05:37 +0900 Subject: [PATCH 243/516] =?UTF-8?q?Feat:=20=EC=82=AC=EC=A7=84=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20null=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/image/converter/ImageConverter.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java b/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java index be2ef4fa..82b9ee86 100644 --- a/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java +++ b/src/main/java/com/otakumap/domain/image/converter/ImageConverter.java @@ -6,6 +6,11 @@ public class ImageConverter { public static ImageResponseDTO.ImageDTO toImageDTO(Image image) { + + if(image == null) { + return new ImageResponseDTO.ImageDTO(); + } + return ImageResponseDTO.ImageDTO.builder() .id(image.getId()) .uuid(image.getUuid()) From 0320eb0f40d8ac551dc2614007ec7b3a81998137 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:10:43 +0900 Subject: [PATCH 244/516] =?UTF-8?q?Feat:=20PlaceRevieDTO=EC=97=90=20type?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 196fba44..83e0f941 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -34,6 +34,7 @@ public static class PlaceReviewDTO { private Long view; private LocalDateTime createdAt; private ImageResponseDTO.ImageDTO reviewImage; + String type; } @Builder From 8dae23d98083fdc92073bd7c8af32d3872eef5cf Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:11:59 +0900 Subject: [PATCH 245/516] =?UTF-8?q?Feat:=20toPlaceReviewDTO=EC=97=90=20typ?= =?UTF-8?q?e(place)=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20reviewImage=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_review/converter/PlaceReviewConverter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 548d3b7a..79ea127f 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -43,7 +43,8 @@ public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview .content(placeReview.getContent()) .view(placeReview.getView()) .createdAt(placeReview.getCreatedAt()) - .reviewImage(ImageConverter.toImageDTO(placeReview.getImage())) + .reviewImage(ImageConverter.toImageDTO(placeReview.getImages().get(0))) // 나중에 수정 + .type("place") .build(); } From 65d21279184c439d3ab40685d05fe109e0e729ff Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:13:04 +0900 Subject: [PATCH 246/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/converter/EventReviewConverter.java | 8 ++++---- .../com/otakumap/domain/user/converter/UserConverter.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java index 34a9c85b..33f97e6a 100644 --- a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java +++ b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java @@ -12,10 +12,10 @@ public class EventReviewConverter { public static EventReviewResponseDTO.EventReviewPreViewDTO eventReviewPreViewDTO(EventReview eventReview) { ImageResponseDTO.ImageDTO image = ImageResponseDTO.ImageDTO.builder() - .id(eventReview.getImage().getId()) - .uuid(eventReview.getImage().getUuid()) - .fileUrl(eventReview.getImage().getFileUrl()) - .fileName(eventReview.getImage().getFileName()) + .id(eventReview.getImages().get(0).getId()) + .uuid(eventReview.getImages().get(0).getUuid()) + .fileUrl(eventReview.getImages().get(0).getFileUrl()) + .fileName(eventReview.getImages().get(0).getFileName()) .build(); return EventReviewResponseDTO.EventReviewPreViewDTO.builder() diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 52828de3..90aa62e3 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -111,7 +111,7 @@ public static UserResponseDTO.UserReviewDTO reviewDTO(PlaceReview review) { .reviewId(review.getId()) .title(review.getTitle()) .content(review.getContent()) - .thumbnail(review.getImage() == null ? null : review.getImage().getFileUrl()) // 이미지 여러 개면 수정 + .thumbnail(review.getImages().get(0) == null ? null : review.getImages().get(0).getFileUrl()) // 이미지 여러 개면 수정 -> 나중에 수정 필요! .views(review.getView()) .createdAt(review.getCreatedAt().toLocalDate()) .build(); From 1475f0531073f350dee60b4df41595d61b9bdebf Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 09:51:32 +0900 Subject: [PATCH 247/516] Fix: correct typos in code --- .../com/otakumap/domain/image/contoller/ImageController.java | 2 +- .../com/otakumap/domain/image/service/ImageCommandService.java | 2 +- .../otakumap/domain/image/service/ImageCommandServiceImpl.java | 2 +- src/main/java/com/otakumap/domain/user/entity/User.java | 2 +- .../otakumap/domain/user/service/UserCommandServiceImpl.java | 3 +-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/image/contoller/ImageController.java b/src/main/java/com/otakumap/domain/image/contoller/ImageController.java index c21ff54f..26b358cb 100644 --- a/src/main/java/com/otakumap/domain/image/contoller/ImageController.java +++ b/src/main/java/com/otakumap/domain/image/contoller/ImageController.java @@ -27,7 +27,7 @@ public class ImageController { public ApiResponse uploadImage(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE), description = "폴더는 profile, review, event 중 하나를 선택해주세요.") @RequestPart("folder") @Valid ImageRequestDTO.uploadDTO folder, @RequestPart("image") MultipartFile image ) { - Image uploadedImage = imageCommandService.uploadaImage(image, folder.getFolder()); + Image uploadedImage = imageCommandService.uploadImage(image, folder.getFolder()); return ApiResponse.onSuccess("이미지가 성공적으로 업로드되었습니다. URL: " + uploadedImage.getFileUrl()); } } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java index 8e9c65d9..9df0bb58 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -8,6 +8,6 @@ public interface ImageCommandService { Image uploadProfileImage(MultipartFile file, Long userId); List uploadReviewImages(List files, Long reviewId); - Image uploadaImage(MultipartFile file, String folder); + Image uploadImage(MultipartFile file, String folder); } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java index fa6c1c52..f335b4ea 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -45,7 +45,7 @@ public List uploadReviewImages(List files, Long reviewId) @Override @Transactional - public Image uploadaImage(MultipartFile file, String folder) { + public Image uploadImage(MultipartFile file, String folder) { String keyName = switch (folder) { case "profile" -> amazonS3Util.generateProfileKeyName(); case "review" -> amazonS3Util.generateReviewKeyName(); diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index b513024a..3e9ae8dd 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -88,7 +88,7 @@ public void setNotification(Integer type, boolean isEnabled) { else { this.isEventBenefitsNotified = isEnabled; } } - public void setProflieImage(Image image) { + public void setProfileImage(Image image) { this.profileImage = image; } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 5e656294..033a3ad8 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -2,7 +2,6 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.service.ImageCommandService; -import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; @@ -74,7 +73,7 @@ public void resetPassword(UserRequestDTO.ResetPasswordDTO request) { @Override public String updateProfileImage(User user, MultipartFile file) { Image image = imageCommandService.uploadProfileImage(file, user.getId()); - user.setProflieImage(image); + user.setProfileImage(image); userRepository.save(user); return image.getFileUrl(); } From b53fa19091880276793fc5c13892dabcbaa5181e Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 16:47:54 +0900 Subject: [PATCH 248/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EA=B3=BC=EC=A0=95=EC=97=90=EC=84=9C=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EA=B2=80=EC=83=89=20API?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AnimationRepository.java | 10 ++++++++ .../controller/ReviewSearchController.java | 23 +++++++++++++++++++ .../reviews/converter/ReviewConverter.java | 21 +++++++++++++++++ .../domain/reviews/dto/ReviewResponseDTO.java | 18 +++++++++++++++ .../reviews/service/ReviewQueryService.java | 4 ++++ .../service/ReviewQueryServiceImpl.java | 10 ++++++++ 6 files changed, 86 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java diff --git a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java new file mode 100644 index 00000000..93ffab4f --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.animation.repository; + +import com.otakumap.domain.animation.entity.Animation; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface AnimationRepository extends JpaRepository { + List getAnimationByNameContaining(String keyword); +} diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index d4842ef8..78dec4bc 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -1,19 +1,29 @@ package com.otakumap.domain.reviews.controller; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.service.ReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.SearchHandler; 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.responses.ApiResponses; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.validation.annotation.Validated; 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 java.util.List; + +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/api") @@ -50,4 +60,17 @@ public ApiResponse> getSearched return ApiResponse.onSuccess(searchResults); } + + @GetMapping("/reviews/animations/search") + @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다.") + public ApiResponse searchAnimation( + @RequestParam @NotBlank(message = "검색어를 입력해주세요") @Pattern(regexp = "^\\S+$", message = "공백은 허용되지 않습니다") String keyword) { + List animationList = reviewQueryService.searchAnimation(keyword); + + if (animationList.isEmpty()) { + throw new SearchHandler(ErrorStatus.ANIMATION_NOT_FOUND); + } + + return ApiResponse.onSuccess(ReviewConverter.animationResultListDTO(animationList)); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 77127c66..aab5c118 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,10 +1,13 @@ package com.otakumap.domain.reviews.converter; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import java.util.List; + public class ReviewConverter { public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7ReviewPreViewDTO(EventReview eventReview) { @@ -41,4 +44,22 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .type("place") .build(); } + + public static ReviewResponseDTO.AnimationResultDTO animationResultDTO(Animation animation) { + return ReviewResponseDTO.AnimationResultDTO.builder() + .animationId(animation.getId()) + .name(animation.getName()) + .build(); + } + + public static ReviewResponseDTO.AnimationResultListDTO animationResultListDTO(List animations) { + List animationResultDTOs = animations.stream() + .map(ReviewConverter::animationResultDTO) + .toList(); + + return ReviewResponseDTO.AnimationResultListDTO.builder() + .animations(animationResultDTOs) + .listSize(animationResultDTOs.size()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index ab50a926..4de2878b 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -44,4 +44,22 @@ public static class SearchedReviewPreViewDTO { LocalDateTime createdAt; ImageResponseDTO.ImageDTO reviewImage; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationResultDTO { + Long animationId; + String name; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationResultListDTO { + List animations; + Integer listSize; + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index e29c966d..04d98250 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -1,8 +1,12 @@ package com.otakumap.domain.reviews.service; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import org.springframework.data.domain.Page; +import java.util.List; + public interface ReviewQueryService { Page searchReviewsByKeyword(String keyword, int page, int size, String sort); + List searchAnimation(String keyword); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 24c019c4..72b46582 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -1,5 +1,7 @@ package com.otakumap.domain.reviews.service; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.repository.AnimationRepository; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; import lombok.RequiredArgsConstructor; @@ -7,16 +9,24 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class ReviewQueryServiceImpl implements ReviewQueryService { private final ReviewRepositoryCustom reviewRepositoryCustom; + private final AnimationRepository animationRepository; @Override public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { return reviewRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } + + @Override + public List searchAnimation(String keyword) { + return animationRepository.getAnimationByNameContaining(keyword); + } } From efea1efef7b875d08eeb7244692b58a9cab6c4c0 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 17:23:52 +0900 Subject: [PATCH 249/516] =?UTF-8?q?Feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/controller/ReviewSearchController.java | 5 +++-- .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index 78dec4bc..2b90e627 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -62,9 +62,10 @@ public ApiResponse> getSearched } @GetMapping("/reviews/animations/search") - @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다.") + @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다. 공백은 허용되지 않습니다.") public ApiResponse searchAnimation( - @RequestParam @NotBlank(message = "검색어를 입력해주세요") @Pattern(regexp = "^\\S+$", message = "공백은 허용되지 않습니다") String keyword) { + @RequestParam @NotBlank(message = "검색어를 입력해주세요") + @Pattern(regexp = "^[^\\s].*$", message = "첫 글자는 공백이 될 수 없습니다") String keyword) { List animationList = reviewQueryService.searchAnimation(keyword); if (animationList.isEmpty()) { diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 15448db2..66e42c56 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -70,7 +70,10 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_SORT_TYPE(HttpStatus.BAD_REQUEST, "SORT4001", "유효하지 않은 정렬 기준입니다."), // 이미지 관련 에러 - INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."); + INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), + + // 검색 관련 에러 + INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."); private final HttpStatus httpStatus; private final String code; From f14a8564c1564069d605f42533bb8693d5dd242b Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 17:29:25 +0900 Subject: [PATCH 250/516] =?UTF-8?q?Feat:=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/repository/AnimationRepository.java | 5 ++++- .../domain/reviews/service/ReviewQueryServiceImpl.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java index 93ffab4f..f90e98f1 100644 --- a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java +++ b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java @@ -2,9 +2,12 @@ import com.otakumap.domain.animation.entity.Animation; 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 AnimationRepository extends JpaRepository { - List getAnimationByNameContaining(String keyword); + @Query("SELECT a FROM Animation a WHERE REPLACE(LOWER(a.name), ' ', '') LIKE CONCAT('%', REPLACE(LOWER(:keyword), ' ', ''), '%')") + List searchAnimationByKeyword(@Param("keyword") String keyword); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 72b46582..e0765ddf 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -27,6 +27,6 @@ public Page searchReviewsByKeyword(S @Override public List searchAnimation(String keyword) { - return animationRepository.getAnimationByNameContaining(keyword); + return animationRepository.searchAnimationByKeyword(keyword); } } From 64a0f2b003a92d96c0a986d09655401e4e7a0b00 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 17:45:00 +0900 Subject: [PATCH 251/516] =?UTF-8?q?Refactor:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20(review=20->=20animation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../animation/DTO/AnimationResponseDTO.java | 28 ++++++++++++ .../controller/AnimationController.java | 44 +++++++++++++++++++ .../converter/AnimationConverter.java | 27 ++++++++++++ .../service/AnimationQueryService.java | 9 ++++ .../service/AnimationQueryServiceImpl.java | 14 ++++++ .../controller/ReviewSearchController.java | 22 ---------- .../reviews/converter/ReviewConverter.java | 21 --------- .../domain/reviews/dto/ReviewResponseDTO.java | 18 -------- .../reviews/service/ReviewQueryService.java | 4 -- .../service/ReviewQueryServiceImpl.java | 9 ---- 10 files changed, 122 insertions(+), 74 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/animation/controller/AnimationController.java create mode 100644 src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java b/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java new file mode 100644 index 00000000..68459901 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.animation.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class AnimationResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationResultDTO { + Long animationId; + String name; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationResultListDTO { + List animations; + Integer listSize; + } +} diff --git a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java new file mode 100644 index 00000000..484d20aa --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java @@ -0,0 +1,44 @@ +package com.otakumap.domain.animation.controller; + +import com.otakumap.domain.animation.DTO.AnimationResponseDTO; +import com.otakumap.domain.animation.converter.AnimationConverter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.service.AnimationQueryService; +import com.otakumap.domain.reviews.converter.ReviewConverter; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.SearchHandler; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +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 java.util.List; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/animations") +public class AnimationController { + private final AnimationQueryService animationQueryService; + + @GetMapping("/search") + @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다. 공백은 허용되지 않습니다.") + public ApiResponse searchAnimation( + @RequestParam @NotBlank(message = "검색어를 입력해주세요") + @Pattern(regexp = "^[^\\s].*$", message = "첫 글자는 공백이 될 수 없습니다") String keyword) { + List animationList = animationQueryService.searchAnimation(keyword); + + if (animationList.isEmpty()) { + throw new SearchHandler(ErrorStatus.ANIMATION_NOT_FOUND); + } + + return ApiResponse.onSuccess(AnimationConverter.animationResultListDTO(animationList)); + } +} diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java new file mode 100644 index 00000000..016fb929 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.animation.converter; + +import com.otakumap.domain.animation.DTO.AnimationResponseDTO; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.reviews.converter.ReviewConverter; + +import java.util.List; + +public class AnimationConverter { + public static AnimationResponseDTO.AnimationResultDTO animationResultDTO(Animation animation) { + return AnimationResponseDTO.AnimationResultDTO.builder() + .animationId(animation.getId()) + .name(animation.getName()) + .build(); + } + + public static AnimationResponseDTO.AnimationResultListDTO animationResultListDTO(List animations) { + List animationResultDTOs = animations.stream() + .map(AnimationConverter::animationResultDTO) + .toList(); + + return AnimationResponseDTO.AnimationResultListDTO.builder() + .animations(animationResultDTOs) + .listSize(animationResultDTOs.size()) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java new file mode 100644 index 00000000..1e5924ba --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.entity.Animation; + +import java.util.List; + +public interface AnimationQueryService { + List searchAnimation(String keyword); +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java new file mode 100644 index 00000000..78c9b225 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.entity.Animation; + +import java.util.List; + +public class AnimationQueryServiceImpl implements AnimationQueryService { + private final AnimationRepository animationRepository; + + @Override + public List searchAnimation(String keyword) { + return animationRepository.searchAnimationByKeyword(keyword); + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index 2b90e627..7c6e437d 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -1,18 +1,12 @@ package com.otakumap.domain.reviews.controller; -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.service.ReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.SearchHandler; 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.responses.ApiResponses; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.validation.annotation.Validated; @@ -21,8 +15,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @Validated @RestController @RequiredArgsConstructor @@ -60,18 +52,4 @@ public ApiResponse> getSearched return ApiResponse.onSuccess(searchResults); } - - @GetMapping("/reviews/animations/search") - @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다. 공백은 허용되지 않습니다.") - public ApiResponse searchAnimation( - @RequestParam @NotBlank(message = "검색어를 입력해주세요") - @Pattern(regexp = "^[^\\s].*$", message = "첫 글자는 공백이 될 수 없습니다") String keyword) { - List animationList = reviewQueryService.searchAnimation(keyword); - - if (animationList.isEmpty()) { - throw new SearchHandler(ErrorStatus.ANIMATION_NOT_FOUND); - } - - return ApiResponse.onSuccess(ReviewConverter.animationResultListDTO(animationList)); - } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index aab5c118..77127c66 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,13 +1,10 @@ package com.otakumap.domain.reviews.converter; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import java.util.List; - public class ReviewConverter { public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7ReviewPreViewDTO(EventReview eventReview) { @@ -44,22 +41,4 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .type("place") .build(); } - - public static ReviewResponseDTO.AnimationResultDTO animationResultDTO(Animation animation) { - return ReviewResponseDTO.AnimationResultDTO.builder() - .animationId(animation.getId()) - .name(animation.getName()) - .build(); - } - - public static ReviewResponseDTO.AnimationResultListDTO animationResultListDTO(List animations) { - List animationResultDTOs = animations.stream() - .map(ReviewConverter::animationResultDTO) - .toList(); - - return ReviewResponseDTO.AnimationResultListDTO.builder() - .animations(animationResultDTOs) - .listSize(animationResultDTOs.size()) - .build(); - } } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 4de2878b..ab50a926 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -44,22 +44,4 @@ public static class SearchedReviewPreViewDTO { LocalDateTime createdAt; ImageResponseDTO.ImageDTO reviewImage; } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class AnimationResultDTO { - Long animationId; - String name; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class AnimationResultListDTO { - List animations; - Integer listSize; - } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index 04d98250..e29c966d 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -1,12 +1,8 @@ package com.otakumap.domain.reviews.service; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import org.springframework.data.domain.Page; -import java.util.List; - public interface ReviewQueryService { Page searchReviewsByKeyword(String keyword, int page, int size, String sort); - List searchAnimation(String keyword); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index e0765ddf..3e5c1d0c 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -1,6 +1,5 @@ package com.otakumap.domain.reviews.service; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.repository.AnimationRepository; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; @@ -9,24 +8,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class ReviewQueryServiceImpl implements ReviewQueryService { private final ReviewRepositoryCustom reviewRepositoryCustom; - private final AnimationRepository animationRepository; @Override public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { return reviewRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } - - @Override - public List searchAnimation(String keyword) { - return animationRepository.searchAnimationByKeyword(keyword); - } } From 0278789cf79cc466b971b5bdefb492308f3e18be Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 17:46:32 +0900 Subject: [PATCH 252/516] =?UTF-8?q?Refactor:=20unused=20import=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/controller/AnimationController.java | 2 -- .../otakumap/domain/animation/converter/AnimationConverter.java | 1 - .../domain/reviews/controller/ReviewSearchController.java | 2 -- .../otakumap/domain/reviews/service/ReviewQueryServiceImpl.java | 1 - 4 files changed, 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java index 484d20aa..db6e7cf7 100644 --- a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java +++ b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java @@ -4,8 +4,6 @@ import com.otakumap.domain.animation.converter.AnimationConverter; import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.service.AnimationQueryService; -import com.otakumap.domain.reviews.converter.ReviewConverter; -import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.SearchHandler; diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java index 016fb929..a342df28 100644 --- a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -2,7 +2,6 @@ import com.otakumap.domain.animation.DTO.AnimationResponseDTO; import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.reviews.converter.ReviewConverter; import java.util.List; diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index 7c6e437d..d4842ef8 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -9,13 +9,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -import org.springframework.validation.annotation.Validated; 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; -@Validated @RestController @RequiredArgsConstructor @RequestMapping("/api") diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 3e5c1d0c..24c019c4 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -1,6 +1,5 @@ package com.otakumap.domain.reviews.service; -import com.otakumap.domain.animation.repository.AnimationRepository; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; import lombok.RequiredArgsConstructor; From a4142d5d9e4adf90ace2b05dc172e94f35e44bd8 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 17:59:27 +0900 Subject: [PATCH 253/516] =?UTF-8?q?Fix:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../animation/service/AnimationQueryServiceImpl.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java index 78c9b225..984d60e6 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -1,13 +1,20 @@ package com.otakumap.domain.animation.service; import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.repository.AnimationRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import java.util.List; +@Service +@RequiredArgsConstructor public class AnimationQueryServiceImpl implements AnimationQueryService { private final AnimationRepository animationRepository; @Override + @Transactional public List searchAnimation(String keyword) { return animationRepository.searchAnimationByKeyword(keyword); } From 6d88eb96b32831fa4bb32875c5e06160cb352b65 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 30 Jan 2025 18:58:31 +0900 Subject: [PATCH 254/516] =?UTF-8?q?Feat:=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../animation/DTO/AnimationRequestDTO.java | 12 ++++++ .../animation/DTO/AnimationResponseDTO.java | 11 +++++ .../controller/AnimationController.java | 17 ++++++-- .../converter/AnimationConverter.java | 14 ++++++ .../domain/animation/entity/Animation.java | 2 +- .../service/AnimationCommandService.java | 7 +++ .../service/AnimationCommandServiceImpl.java | 43 +++++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 4 ++ .../exception/handler/AnimationHandler.java | 8 ++++ 9 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/animation/DTO/AnimationRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java diff --git a/src/main/java/com/otakumap/domain/animation/DTO/AnimationRequestDTO.java b/src/main/java/com/otakumap/domain/animation/DTO/AnimationRequestDTO.java new file mode 100644 index 00000000..deda6271 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/DTO/AnimationRequestDTO.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.animation.DTO; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +public class AnimationRequestDTO { + @Getter + public static class AnimationCreationRequestDTO { + @NotBlank + String name; + } +} diff --git a/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java b/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java index 68459901..cdcd1fac 100644 --- a/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/animation/DTO/AnimationResponseDTO.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; import java.util.List; public class AnimationResponseDTO { @@ -25,4 +26,14 @@ public static class AnimationResultListDTO { List animations; Integer listSize; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationCreationResponseDTO { + Long animationId; + String name; + LocalDateTime createdAt; + } } diff --git a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java index db6e7cf7..555fbf49 100644 --- a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java +++ b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java @@ -1,21 +1,21 @@ package com.otakumap.domain.animation.controller; +import com.otakumap.domain.animation.DTO.AnimationRequestDTO; import com.otakumap.domain.animation.DTO.AnimationResponseDTO; import com.otakumap.domain.animation.converter.AnimationConverter; import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.service.AnimationCommandService; import com.otakumap.domain.animation.service.AnimationQueryService; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.SearchHandler; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -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; @@ -25,6 +25,7 @@ @RequestMapping("/api/animations") public class AnimationController { private final AnimationQueryService animationQueryService; + private final AnimationCommandService animationCommandService; @GetMapping("/search") @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다. 공백은 허용되지 않습니다.") @@ -39,4 +40,12 @@ public ApiResponse searchAnimation( return ApiResponse.onSuccess(AnimationConverter.animationResultListDTO(animationList)); } + + @PostMapping + @Operation(summary = "애니메이션 등록", description = "원하는 애니메이션이 없을 경우, 사용자가 애니메이션을 직접 등록합니다.") + public ApiResponse createAnimation( + @RequestBody @Valid AnimationRequestDTO.AnimationCreationRequestDTO request) { + Animation animation = animationCommandService.createAnimation(request.getName()); + return ApiResponse.onSuccess(AnimationConverter.toAnimationCreationResponseDTO(animation)); + } } diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java index a342df28..6b5090c7 100644 --- a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -23,4 +23,18 @@ public static AnimationResponseDTO.AnimationResultListDTO animationResultListDTO .listSize(animationResultDTOs.size()) .build(); } + + public static AnimationResponseDTO.AnimationCreationResponseDTO toAnimationCreationResponseDTO(Animation animation) { + return AnimationResponseDTO.AnimationCreationResponseDTO.builder() + .animationId(animation.getId()) + .name(animation.getName()) + .createdAt(animation.getCreatedAt()) + .build(); + } + + public static Animation toAnimation(String name) { + return Animation.builder() + .name(name) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index 1259b0c9..cd853f0e 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -15,6 +15,6 @@ public class Animation extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 50) private String name; } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java new file mode 100644 index 00000000..1ebbc25d --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.entity.Animation; + +public interface AnimationCommandService { + Animation createAnimation(String name); +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java new file mode 100644 index 00000000..bc6778a0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java @@ -0,0 +1,43 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.converter.AnimationConverter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.repository.AnimationRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AnimationCommandServiceImpl implements AnimationCommandService { + private final AnimationRepository animationRepository; + + @Override + @Transactional + public Animation createAnimation(String name) { + if (name == null || name.trim().isEmpty()) { + throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_IS_EMPTY); + } + + if (name.length() < 2 || name.length() > 50) { + throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_LENGTH); + } + + if (!name.matches("^[가-힣a-zA-Z0-9\\\\s./()-]+$")) { + throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_SPECIAL_CHARACTER); + } + + String normalizedName = name.replaceAll("\\s+", "").toLowerCase(); + + // 중복 검사 + if (animationRepository.findAll().stream() + .anyMatch(animation -> animation.getName().replaceAll("\\s+", "").toLowerCase().equals(normalizedName))) { + throw new AnimationHandler(ErrorStatus.ANIMATION_ALREADY_EXISTS); + } + + Animation newAnimation = AnimationConverter.toAnimation(name); + return animationRepository.save(newAnimation); + } +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 66e42c56..2f1c4fce 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -52,6 +52,10 @@ public enum ErrorStatus implements BaseErrorCode { // 애니메이션 관련 에러 ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4001", "존재하지 않는 애니메이션입니다"), PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4002", "존재하지 않는 애니메이션입니다"), + ANIMATION_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ANIMATION4003", "이미 존재하는 애니메이션입니다."), + ANIMATION_NAME_IS_EMPTY(HttpStatus.BAD_REQUEST, "ANIMATION4004", "애니메이션 이름이 비어있습니다."), + ANIMATION_NAME_LENGTH(HttpStatus.BAD_REQUEST, "ANIMATION4005", "애니메이션 이름은 2자 이상 50자 이하여야 합니다."), + ANIMATION_NAME_SPECIAL_CHARACTER(HttpStatus.BAD_REQUEST, "ANIMATION4006", "애니메이션 이름은 한글, 영문, 숫자, 공백 및 일부 특수문자(./-)만 포함할 수 있습니다."), // 루트 관련 에러 ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java new file mode 100644 index 00000000..25c8fff1 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java @@ -0,0 +1,8 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class AnimationHandler extends GeneralException { + public AnimationHandler(BaseErrorCode errorCode) { super(errorCode); } +} \ No newline at end of file From 467ec35b6b453718470af26120b31726c962cf82 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 31 Jan 2025 15:22:05 +0900 Subject: [PATCH 255/516] =?UTF-8?q?Feat:=20uri=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/controller/RouteLikeController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index c857d609..a737a6b8 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -17,7 +17,7 @@ import java.util.List; @RestController -@RequestMapping("/api") +@RequestMapping("/api/route-likes") @RequiredArgsConstructor @Validated public class RouteLikeController { @@ -25,7 +25,7 @@ public class RouteLikeController { private final RouteLikeCommandService routeLikeCommandService; @Operation(summary = "루트 저장", description = "루트를 저장합니다.") - @PostMapping("/routes/{routeId}") + @PostMapping("/{routeId}") @Parameters({ @Parameter(name = "routeId", description = "루트 Id") }) @@ -37,7 +37,7 @@ public ApiResponse saveRouteLike(@PathVariable Long routeId, @CurrentUse } @Operation(summary = "저장된 루트 삭제", description = "저장된 루트를 삭제합니다.") - @DeleteMapping("/routes/liked") + @DeleteMapping("") @Parameters({ @Parameter(name = "routeIds", description = "저장된 루트 id List"), }) @@ -47,7 +47,7 @@ public ApiResponse deleteSavedRoute(@RequestParam(required = false) @Exi } @Operation(summary = "루트 제목 편집", description = "루트의 제목을 편집(수정)합니다.") - @PatchMapping("/routes/{routeId}/name") + @PatchMapping("/{routeId}/name") @Parameters({ @Parameter(name = "routeId", description = "루트 Id") }) From ee85f9f1cef30f1bfbe1dc4962507b09fe5451f9 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 31 Jan 2025 15:25:59 +0900 Subject: [PATCH 256/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=EB=A5=BC=20=EB=88=84=EB=A5=B8=20=EB=AA=85?= =?UTF-8?q?=EC=86=8C=EB=A5=BC=20=EB=8B=A4=EC=8B=9C=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=EB=A5=BC=20=EB=88=84=EB=A5=BC=20=EC=8B=9C,=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/repository/PlaceLikeRepository.java | 3 +++ .../place_like/service/PlaceLikeCommandServiceImpl.java | 5 ++++- .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index ab6300c3..19a07d8e 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.repository; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; @@ -17,4 +18,6 @@ public interface PlaceLikeRepository extends JpaRepository { Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); + + boolean existsByUserAndPlace(User user, Place place); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java index 7c716a84..3caa5ba7 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -35,8 +35,11 @@ public void savePlaceLike(User user, Long placeId) { Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - PlaceLike placeLike = PlaceLikeConverter.toPlaceLike(user, place); + if (placeLikeRepository.existsByUserAndPlace(user, place)) { + throw new PlaceHandler(ErrorStatus.PLACE_LIKE_ALREADY_EXISTS); + } + PlaceLike placeLike = PlaceLikeConverter.toPlaceLike(user, place); placeLikeRepository.save(placeLike); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 80b0d76a..05d65476 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -43,7 +43,8 @@ public enum ErrorStatus implements BaseErrorCode { EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."), // 명소 좋아요 관련 에러 - PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4002", "저장되지 않은 명소입니다."), + PLACE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4003", "저장되지 않은 명소입니다."), + PLACE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "PLACE4002", "이미 좋아요를 누른 명소입니다."), // 후기 검색 관련 에러 REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), From 330fc5f573046dd7e6b57044359372a664ce4b89 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Fri, 31 Jan 2025 23:21:24 +0900 Subject: [PATCH 257/516] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=88=98=EA=B0=80=20=EA=B0=99=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=B5=9C=EC=8B=A0=20=EA=B8=80=EC=9D=B4=20=EB=8D=94?= =?UTF-8?q?=20=EC=83=81=EB=8B=A8=EC=97=90=20=EB=9C=A8=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/reviews/converter/ReviewConverter.java | 2 ++ .../com/otakumap/domain/reviews/dto/ReviewResponseDTO.java | 1 + .../domain/reviews/repository/ReviewRepositoryImpl.java | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 22d9b004..6f3bcefe 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -13,6 +13,7 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO .title(eventReview.getTitle()) .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) .view(eventReview.getView()) + .createdAt(eventReview.getCreatedAt()) .type("event") .build(); } @@ -23,6 +24,7 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO .title(eventReview.getTitle()) .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) .view(eventReview.getView()) + .createdAt(eventReview.getCreatedAt()) .type("place") .build(); } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 8c13f062..15d3adb9 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -29,6 +29,7 @@ public static class Top7ReviewPreViewDTO { ImageResponseDTO.ImageDTO reviewImage; Long view; String type; + LocalDateTime createdAt; } @Builder diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 797842c5..b8f2d932 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -134,7 +134,8 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { eventReviews.stream() .map(ReviewConverter::toTop7EventReviewPreViewDTO) ) - .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView).reversed()) + .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView).reversed() + .thenComparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getCreatedAt).reversed()) .limit(7) .collect(Collectors.toList()); From 963d013f79b1762a3c0d7cb58f2baf63e52ec9d7 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 31 Jan 2025 23:34:57 +0900 Subject: [PATCH 258/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0/?= =?UTF-8?q?=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EC=B7=A8=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_like/controller/PlaceLikeController.java | 9 +++++++++ .../place_like/converter/PlaceLikeConverter.java | 7 +++++++ .../domain/place_like/dto/PlaceLikeRequestDTO.java | 12 ++++++++++++ .../domain/place_like/dto/PlaceLikeResponseDTO.java | 10 ++++++++++ .../otakumap/domain/place_like/entity/PlaceLike.java | 3 +++ .../place_like/service/PlaceLikeCommandService.java | 4 ++++ .../service/PlaceLikeCommandServiceImpl.java | 8 ++++++++ 7 files changed, 53 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 7f80021f..996f7427 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -1,6 +1,8 @@ package com.otakumap.domain.place_like.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.place_like.converter.PlaceLikeConverter; +import com.otakumap.domain.place_like.dto.PlaceLikeRequestDTO; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.service.PlaceLikeCommandService; import com.otakumap.domain.place_like.service.PlaceLikeQueryService; @@ -10,6 +12,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -55,4 +58,10 @@ public ApiResponse savePlaceLike(@PathVariable Long placeId, @CurrentUse return ApiResponse.onSuccess("장소가 성공적으로 저장되었습니다."); } + + @Operation(summary = "저장된 장소 즐겨찾기/즐겨찾기 취소", description = "저장된 장소를 즐겨찾기 또는 취소합니다.") + @PatchMapping("/{placeLikeId}/favorites") + public ApiResponse favoritePlaceLike(@PathVariable Long placeLikeId, @RequestBody @Valid PlaceLikeRequestDTO.FavoriteDTO request) { + return ApiResponse.onSuccess(PlaceLikeConverter.toFavoriteResultDTO(placeLikeCommandService.favoritePlaceLike(placeLikeId, request))); + } } diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 1a3b2764..a2872ee6 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -36,4 +36,11 @@ public static PlaceLike toPlaceLike(User user, Place place) { .isFavorite(Boolean.TRUE) .build(); } + + public static PlaceLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(PlaceLike placeLike) { + return PlaceLikeResponseDTO.FavoriteResultDTO.builder() + .placeLikeId(placeLike.getId()) + .isFavorite(placeLike.getIsFavorite()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java new file mode 100644 index 00000000..9ccbad05 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.place_like.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class PlaceLikeRequestDTO { + @Getter + public static class FavoriteDTO { + @NotNull(message = "즐겨찾기 여부 입력은 필수입니다.") + Boolean isFavorite; + } +} diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 2469fc7f..c81e69c3 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.dto; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -31,4 +32,13 @@ public static class PlaceLikePreViewListDTO { boolean hasNext; Long lastId; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class FavoriteResultDTO { + Long placeLikeId; + Boolean isFavorite; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java index 487c1b87..07210a77 100644 --- a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java +++ b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java @@ -35,4 +35,7 @@ public class PlaceLike extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; + public void setIsFavorite(boolean isFavorite) { + this.isFavorite = isFavorite; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java index 5958a60b..02722557 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java @@ -1,5 +1,7 @@ package com.otakumap.domain.place_like.service; +import com.otakumap.domain.place_like.dto.PlaceLikeRequestDTO; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; import org.springframework.stereotype.Service; @@ -10,4 +12,6 @@ public interface PlaceLikeCommandService { void deletePlaceLike(List placeIds); void savePlaceLike(User user, Long placeId); + + PlaceLike favoritePlaceLike(Long placeLikeId, PlaceLikeRequestDTO.FavoriteDTO request); } diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java index 3caa5ba7..72a62ef8 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -3,6 +3,7 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_like.converter.PlaceLikeConverter; +import com.otakumap.domain.place_like.dto.PlaceLikeRequestDTO; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.user.entity.User; @@ -42,4 +43,11 @@ public void savePlaceLike(User user, Long placeId) { PlaceLike placeLike = PlaceLikeConverter.toPlaceLike(user, place); placeLikeRepository.save(placeLike); } + + @Override + public PlaceLike favoritePlaceLike(Long placeLikeId, PlaceLikeRequestDTO.FavoriteDTO request) { + PlaceLike placeLike = placeLikeRepository.findById(placeLikeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + placeLike.setIsFavorite(request.getIsFavorite()); + return placeLikeRepository.save(placeLike); + } } \ No newline at end of file From 2078b663a77f93380c33e937868e416b271b8e36 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 31 Jan 2025 23:36:13 +0900 Subject: [PATCH 259/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=A0=20=EB=95=8C=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20false=EB=A1=9C=20=EC=A0=80=EC=9E=A5=EB=90=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/converter/PlaceLikeConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index a2872ee6..c4f83af5 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -33,7 +33,7 @@ public static PlaceLike toPlaceLike(User user, Place place) { return PlaceLike.builder() .user(user) .place(place) - .isFavorite(Boolean.TRUE) + .isFavorite(Boolean.FALSE) .build(); } From bb902c775ee720d7fd174dc101d8f17026a21984 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Fri, 31 Jan 2025 23:38:33 +0900 Subject: [PATCH 260/516] =?UTF-8?q?Rename:=20ReviewSearchController->Revie?= =?UTF-8?q?wController=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ReviewSearchController.java => ReviewController.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/com/otakumap/domain/reviews/controller/{ReviewSearchController.java => ReviewController.java} (98%) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java similarity index 98% rename from src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java rename to src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index 915d88aa..24017c7c 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -14,7 +14,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api") -public class ReviewSearchController { +public class ReviewController { private final ReviewQueryService reviewQueryService; From 55852eea71302779280916739eae9f1a9850fccb Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 31 Jan 2025 23:38:37 +0900 Subject: [PATCH 261/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=A0=20=EB=95=8C=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20false=EB=A1=9C=20=EC=A0=80=EC=9E=A5=EB=90=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/converter/PlaceLikeConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index c4f83af5..abde0e4a 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -33,7 +33,7 @@ public static PlaceLike toPlaceLike(User user, Place place) { return PlaceLike.builder() .user(user) .place(place) - .isFavorite(Boolean.FALSE) + .isFavorite(false) .build(); } From dfb684685ab345b364060891cda5f9defe7528e3 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Fri, 31 Jan 2025 23:40:04 +0900 Subject: [PATCH 262/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20n?= =?UTF-8?q?ull=20=EC=97=AC=EB=B6=80=20=EC=B2=B4=ED=81=AC,=20createdAt=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EA=B0=92=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/converter/ReviewConverter.java | 8 ++++++-- .../domain/reviews/repository/ReviewRepositoryImpl.java | 2 +- src/main/java/com/otakumap/global/common/BaseEntity.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 6f3bcefe..be741f22 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -11,7 +11,9 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .reviewImage(eventReview.getImage() != null ? + ImageConverter.toImageDTO(eventReview.getImage()) : + null) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") @@ -22,7 +24,9 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .reviewImage(eventReview.getImage() != null ? + ImageConverter.toImageDTO(eventReview.getImage()) : + null) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("place") diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index b8f2d932..6e04663d 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -134,7 +134,7 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { eventReviews.stream() .map(ReviewConverter::toTop7EventReviewPreViewDTO) ) - .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView).reversed() + .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView) .thenComparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getCreatedAt).reversed()) .limit(7) .collect(Collectors.toList()); diff --git a/src/main/java/com/otakumap/global/common/BaseEntity.java b/src/main/java/com/otakumap/global/common/BaseEntity.java index 0adea0bd..73a7ea96 100644 --- a/src/main/java/com/otakumap/global/common/BaseEntity.java +++ b/src/main/java/com/otakumap/global/common/BaseEntity.java @@ -16,7 +16,7 @@ public class BaseEntity { @CreatedDate - @Column(updatable = false) + @Column(updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private LocalDateTime createdAt; @LastModifiedDate From 9ffaf53736975726e376ca1520f100fd81fb3d4b Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 31 Jan 2025 23:55:47 +0900 Subject: [PATCH 263/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0/?= =?UTF-8?q?=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EC=B7=A8=EC=86=8C=20api?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteLikeController.java | 8 ++++++++ .../converter/RouteLikeConverter.java | 8 ++++++++ .../route_like/dto/RouteLikeRequestDTO.java | 12 ++++++++++++ .../route_like/dto/RouteLikeResponseDTO.java | 17 +++++++++++++++++ .../domain/route_like/entity/RouteLike.java | 4 ++++ .../service/RouteLikeCommandService.java | 4 ++++ .../service/RouteLikeCommandServiceImpl.java | 9 +++++++++ 7 files changed, 62 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index a737a6b8..bd3bc8a0 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -1,6 +1,9 @@ package com.otakumap.domain.route_like.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.route_like.converter.RouteLikeConverter; +import com.otakumap.domain.route_like.dto.RouteLikeRequestDTO; +import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; import com.otakumap.domain.route_like.dto.UpdateNameRequestDTO; import com.otakumap.domain.route_like.service.RouteLikeCommandService; import com.otakumap.domain.user.entity.User; @@ -57,4 +60,9 @@ public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @Requ return ApiResponse.onSuccess("루트 제목이 성공적으로 수정되었습니다."); } + @Operation(summary = "저장된 루트 즐겨찾기/즐겨찾기 취소", description = "저장된 루트를 즐겨찾기 또는 취소합니다.") + @PatchMapping("/{routeLikeId}/favorites") + public ApiResponse favoriteRouteLike(@PathVariable Long routeLikeId, @RequestBody @Valid RouteLikeRequestDTO.FavoriteDTO request) { + return ApiResponse.onSuccess(RouteLikeConverter.toFavoriteResultDTO(routeLikeCommandService.favoriteRouteLike(routeLikeId, request))); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index 77b01e67..f3f1c39e 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -1,6 +1,7 @@ package com.otakumap.domain.route_like.converter; import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.User; @@ -14,4 +15,11 @@ public static RouteLike toRouteLike(User user, Route route) { .isFavorite(Boolean.FALSE) .build(); } + + public static RouteLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(RouteLike routeLike) { + return RouteLikeResponseDTO.FavoriteResultDTO.builder() + .routeLikeId(routeLike.getId()) + .isFavorite(routeLike.getIsFavorite()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java new file mode 100644 index 00000000..26003ba5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.route_like.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class RouteLikeRequestDTO { + @Getter + public static class FavoriteDTO { + @NotNull(message = "즐겨찾기 여부 입력은 필수입니다.") + Boolean isFavorite; + } +} diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java new file mode 100644 index 00000000..e3a7a73e --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java @@ -0,0 +1,17 @@ +package com.otakumap.domain.route_like.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class RouteLikeResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class FavoriteResultDTO { + Long routeLikeId; + Boolean isFavorite; + } +} diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index 5ff32269..e8f8de3e 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -42,4 +42,8 @@ public class RouteLike extends BaseEntity { public void setName(String name) { this.name = name; } + + public void setIsFavorite(boolean isFavorite) { + this.isFavorite = isFavorite; + } } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java index 366fcef5..12e06d79 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -1,5 +1,7 @@ package com.otakumap.domain.route_like.service; +import com.otakumap.domain.route_like.dto.RouteLikeRequestDTO; +import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.User; import org.springframework.stereotype.Service; @@ -10,4 +12,6 @@ public interface RouteLikeCommandService { void saveRouteLike(User user, Long routeId); void deleteRouteLike(List routeIds); void updateName(Long routeId, String name); + + RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.FavoriteDTO request); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index d5f7dfd7..59412d33 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -3,6 +3,7 @@ import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_like.converter.RouteLikeConverter; +import com.otakumap.domain.route_like.dto.RouteLikeRequestDTO; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.route_like.repository.RouteLikeRepository; import com.otakumap.domain.user.entity.User; @@ -57,4 +58,12 @@ public void updateName(Long routeId, String name) { // 변경된 엔티티 저장 routeLikeRepository.save(routeLike); } + + @Override + public RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.FavoriteDTO request) { + RouteLike routeLike = routeLikeRepository.findById(routeLikeId).orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_LIKE_NOT_FOUND)); + routeLike.setIsFavorite(request.getIsFavorite()); + return routeLikeRepository.save(routeLike); + } + } From 83ce6d5efafdbc4aa422881bfe4a2213f9dd0ea3 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 1 Feb 2025 01:44:50 +0900 Subject: [PATCH 264/516] =?UTF-8?q?Feat:=20type=20Enum=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC,=20reviewId=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/controller/ReviewController.java | 8 +++- .../domain/reviews/enums/ReviewType.java | 6 +++ .../reviews/service/ReviewQueryService.java | 3 +- .../service/ReviewQueryServiceImpl.java | 10 +++-- .../apiPayload/code/status/ErrorStatus.java | 4 ++ .../exception/handler/ReviewHandler.java | 10 +++++ .../validation/annotation/ValidReviewId.java | 19 +++++++++ .../validator/ReviewIdValidator.java | 39 +++++++++++++++++++ 8 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/reviews/enums/ReviewType.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/ReviewHandler.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ValidReviewId.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/ReviewIdValidator.java diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index 24017c7c..d7f1f751 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -1,19 +1,23 @@ package com.otakumap.domain.reviews.controller; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.reviews.service.ReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ValidReviewId; 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.responses.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api") +@Validated public class ReviewController { private final ReviewQueryService reviewQueryService; @@ -55,9 +59,9 @@ public ApiResponse> getSearched }) @Parameters({ @Parameter(name = "reviewId", description = "이벤트 or 명소의 후기 id 입니다."), - @Parameter(name = "type", description = "리뷰의 종류를 특정합니다. 'event' 또는 'place' 여야 합니다.") + @Parameter(name = "type", description = "리뷰의 종류를 특정합니다. 'EVENT' 또는 'PLACE' 여야 합니다.") }) - public ApiResponse getReviewDetail(@PathVariable Long reviewId, @RequestParam(defaultValue = "place") String type) { + public ApiResponse getReviewDetail(@PathVariable @ValidReviewId Long reviewId, @RequestParam(defaultValue = "PLACE") ReviewType type) { return ApiResponse.onSuccess(reviewQueryService.getReviewDetail(reviewId, type)); } diff --git a/src/main/java/com/otakumap/domain/reviews/enums/ReviewType.java b/src/main/java/com/otakumap/domain/reviews/enums/ReviewType.java new file mode 100644 index 00000000..46a727d9 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/enums/ReviewType.java @@ -0,0 +1,6 @@ +package com.otakumap.domain.reviews.enums; + +public enum ReviewType { + EVENT, + PLACE +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index e246aba9..1177df2f 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -1,10 +1,11 @@ package com.otakumap.domain.reviews.service; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; import org.springframework.data.domain.Page; public interface ReviewQueryService { Page searchReviewsByKeyword(String keyword, int page, int size, String sort); - ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, String type); + ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewType type); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 85398e16..b84d0ff2 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -6,10 +6,12 @@ import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -31,18 +33,20 @@ public Page searchReviewsByKeyword(S } @Override - public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, String type) { + public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewType type) { - if(type.equals("event")) { + if(type == ReviewType.EVENT) { EventReview eventReview = eventReviewRepository.findById(reviewId) .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); return ReviewConverter.toEventReviewDetailDTO(eventReview); - } else { + } else if (type == ReviewType.PLACE) { PlaceReview placeReview = placeReviewRepository.findById(reviewId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); return ReviewConverter.toPlaceReviewDetailDTO(placeReview); + } else { + throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index c49ee0ad..c84c2791 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -75,6 +75,10 @@ public enum ErrorStatus implements BaseErrorCode { // 정렬 관련 에러 INVALID_SORT_TYPE(HttpStatus.BAD_REQUEST, "SORT4001", "유효하지 않은 정렬 기준입니다."), + // 여행 후기 관련 에러 + INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), + INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), + // 이미지 관련 에러 INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."); diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/ReviewHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/ReviewHandler.java new file mode 100644 index 00000000..69d540b7 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/ReviewHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class ReviewHandler extends GeneralException { + public ReviewHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/otakumap/global/validation/annotation/ValidReviewId.java b/src/main/java/com/otakumap/global/validation/annotation/ValidReviewId.java new file mode 100644 index 00000000..adeb5245 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ValidReviewId.java @@ -0,0 +1,19 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.ReviewIdValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = ReviewIdValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidReviewId { + + String message() default "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."; + Class[] groups() default {}; + Class[] payload() default {}; + +} diff --git a/src/main/java/com/otakumap/global/validation/validator/ReviewIdValidator.java b/src/main/java/com/otakumap/global/validation/validator/ReviewIdValidator.java new file mode 100644 index 00000000..765beda2 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/ReviewIdValidator.java @@ -0,0 +1,39 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ValidReviewId; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ReviewIdValidator implements ConstraintValidator { + + private final EventReviewRepository eventReviewRepository; + private final PlaceReviewRepository placeReviewRepository; + + @Override + public void initialize(ValidReviewId constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long value, ConstraintValidatorContext context) { + + if (eventReviewRepository.existsById(value)) { + return true; + } + + boolean isValid = placeReviewRepository.existsById(value); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.INVALID_REVIEW_ID.toString()).addConstraintViolation(); + } + return isValid; + } +} From 5018d80413793492dbee766a6edaae83c84f3fcf Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 1 Feb 2025 02:36:20 +0900 Subject: [PATCH 265/516] =?UTF-8?q?Feat:=20PlaceAnimation=EA=B3=BC=20Event?= =?UTF-8?q?Animation=EC=9C=BC=EB=A1=9C=20=EC=95=A0=EB=8B=88=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/animation/entity/Animation.java | 11 ----------- .../domain/event_review/entity/EventReview.java | 6 +++--- .../domain/place_review/entity/PlaceReview.java | 7 +------ .../domain/reviews/converter/ReviewConverter.java | 4 ++-- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index 32398b4b..1259b0c9 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -1,14 +1,9 @@ package com.otakumap.domain.animation.entity; -import com.otakumap.domain.event_review.entity.EventReview; -import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @Builder @@ -22,10 +17,4 @@ public class Animation extends BaseEntity { @Column(nullable = false, length = 20) private String name; - - @OneToMany(mappedBy = "animation") - private List eventReviews = new ArrayList<>(); - - @OneToMany(mappedBy = "animation") - private List placeReviews = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 0144a9b8..d915b57f 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -1,8 +1,8 @@ package com.otakumap.domain.event_review.entity; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -54,8 +54,8 @@ public class EventReview extends BaseEntity { private Event event; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "animation_id") - private Animation animation; + @JoinColumn(name = "event_animation_id") + private EventAnimation eventAnimation; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 9202fa27..5d533438 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -1,6 +1,5 @@ package com.otakumap.domain.place_review.entity; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; @@ -53,11 +52,7 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") - private PlaceAnimation placeAnimation; // 리뷰와 PlaceAnimation 연결 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "animation_id") - private Animation animation; + private PlaceAnimation placeAnimation; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 30b3b22a..12c2ae2f 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -48,7 +48,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(placeReview.getId()) - .animationName(placeReview.getAnimation() != null ? placeReview.getAnimation().getName() : null) + .animationName(placeReview.getPlaceAnimation().getAnimation().getName() != null ? placeReview.getPlaceAnimation().getAnimation().getName() : null) .title(placeReview.getTitle()) .view(placeReview.getView()) .content(placeReview.getContent()) @@ -66,7 +66,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(eventReview.getId()) - .animationName(eventReview.getAnimation() != null ? eventReview.getAnimation().getName() : null) + .animationName(eventReview.getEventAnimation().getAnimation().getName() != null ? eventReview.getEventAnimation().getAnimation().getName() : null) .title(eventReview.getTitle()) .view(eventReview.getView()) .content(eventReview.getContent()) From 79690b97e8838af977b897e108e17395af1117b5 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 1 Feb 2025 14:12:03 +0900 Subject: [PATCH 266/516] =?UTF-8?q?Fix:=20view=20=ED=81=B0=20=EC=88=9C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A0=AC=EB=90=98=EA=B2=8C?= =?UTF-8?q?=EB=81=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/repository/ReviewRepositoryImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 6e04663d..32ca16b4 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -134,12 +134,11 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { eventReviews.stream() .map(ReviewConverter::toTop7EventReviewPreViewDTO) ) - .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView) - .thenComparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getCreatedAt).reversed()) + .sorted(Comparator.comparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getView, Comparator.reverseOrder()) + .thenComparing(ReviewResponseDTO.Top7ReviewPreViewDTO::getCreatedAt, Comparator.reverseOrder())) .limit(7) .collect(Collectors.toList()); - return ReviewResponseDTO.Top7ReviewPreViewListDTO.builder() .reviews(top7Reviews) .build(); From dbb8f8ec70fa7bc52c061cd0eb6c3c2ca0e4818f Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 1 Feb 2025 18:30:54 +0900 Subject: [PATCH 267/516] =?UTF-8?q?Feat:=20=EB=A9=94=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=B0=B0=EB=84=88=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 8 ++++++ .../repository/EventRepositoryCustom.java | 3 +++ .../event/repository/EventRepositoryImpl.java | 26 +++++++++++++++++++ .../event/service/EventCustomService.java | 2 ++ .../event/service/EventCustomServiceImpl.java | 6 +++++ 5 files changed, 45 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 1e784b8c..99e42295 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -3,6 +3,8 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.service.EventCustomService; import com.otakumap.domain.event.service.EventQueryService; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.image.entity.Image; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -36,4 +38,10 @@ public ApiResponse> getEventDetail() { public ApiResponse getEventDetail(@PathVariable Long eventId) { return ApiResponse.onSuccess(eventQueryService.getEventDetail(eventId)); } + + @Operation(summary = "홈 화면 이벤트 배너 조회", description = "홈 화면에 띄울 배너 이미지를 불러옵니다.") + @GetMapping("/events/banner") + public ApiResponse getBanner() { + return ApiResponse.onSuccess(eventCustomService.getEventBanner()); + } } diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java index 0ed4c0ee..a5ab245d 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java @@ -1,8 +1,11 @@ package com.otakumap.domain.event.repository; import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.image.dto.ImageResponseDTO; + import java.util.List; public interface EventRepositoryCustom { List getPopularEvents(); + ImageResponseDTO.ImageDTO getEventBanner(); } diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index 6efd3343..0c102c3c 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -4,6 +4,11 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.entity.QEvent; +import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -33,4 +38,25 @@ public List getPopularEvents() { .map(EventConverter::toEventDTO) .collect(Collectors.toList()); } + + @Override + public ImageResponseDTO.ImageDTO getEventBanner() { + QEvent event = QEvent.event; + + Event targetEvent = queryFactory.selectFrom(event) + .where(event.endDate.goe(LocalDate.now()) + .and(event.startDate.loe(LocalDate.now())) + .and(event.thumbnailImage.isNotNull())) + .orderBy(Expressions.numberTemplate(Double.class, "function('rand')").asc()) + .fetchFirst(); + + if (targetEvent == null) { + return ImageResponseDTO.ImageDTO.builder() + .fileUrl("default_banner_url") + .build(); + } + + return ImageConverter.toImageDTO((targetEvent) + .getThumbnailImage()); + } } diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java index 6641a314..b41eaf72 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java @@ -1,9 +1,11 @@ package com.otakumap.domain.event.service; import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.image.dto.ImageResponseDTO; import java.util.List; public interface EventCustomService { List getPopularEvents(); + ImageResponseDTO.ImageDTO getEventBanner(); } diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java index d1ad9060..342dace6 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java @@ -2,6 +2,7 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.repository.EventRepositoryCustom; +import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,4 +18,9 @@ public class EventCustomServiceImpl implements EventCustomService { public List getPopularEvents() { return eventRepository.getPopularEvents(); } + + @Override + public ImageResponseDTO.ImageDTO getEventBanner() { + return eventRepository.getEventBanner(); + } } From 6658d72accc82b76b9984cc58428f60e96e616a3 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sat, 1 Feb 2025 22:07:23 +0900 Subject: [PATCH 268/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/DTO/PlaceResponseDTO.java | 29 +++++++++++++++++++ .../place/controller/PlaceController.java | 21 +++++++++++++- .../place/converter/PlaceConverter.java | 22 ++++++++++++++ .../place/service/PlaceQueryService.java | 9 ++++++ .../place/service/PlaceQueryServiceImpl.java | 21 ++++++++++++++ .../repository/PlaceAnimationRepository.java | 4 ++- 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java new file mode 100644 index 00000000..edc96df2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.place.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class PlaceResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceAnimationListDTO { + List placeAnimations; + int listSize; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceAnimationDTO { + private Long placeAnimationId; + private Long animationId; + private String animationName; + } +} diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java index 306c5c6e..bad20a35 100644 --- a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -1,12 +1,31 @@ package com.otakumap.domain.place.controller; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.converter.PlaceConverter; +import com.otakumap.domain.place.service.PlaceQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ExistPlace; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +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 java.util.List; + @RestController -@RequestMapping("/api") +@RequestMapping("/api/places") @RequiredArgsConstructor public class PlaceController { + private final PlaceQueryService placeQueryService; + + @GetMapping("/{placeId}/animations") + @Operation(summary = "해당 장소와 관련된 애니메이션 목록 조회", description = "해당 장소와 관련된 애니메이션 목록을 조회합니다. PlaceAnimationId도 함께 반환되며, 이를 후기 작성 시 사용할 수 있습니다.") + public ApiResponse getPlaceAnimations(@RequestParam @ExistPlace Long placeId) { + List placeAnimations = placeQueryService.getPlaceAnimations(placeId); + return ApiResponse.onSuccess(PlaceConverter.toPlaceAnimationListDTO(placeAnimations)); + } } diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index 6e1852d4..c672ace6 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -1,7 +1,29 @@ package com.otakumap.domain.place.converter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; import org.springframework.stereotype.Component; +import java.util.List; +import java.util.stream.Collectors; + @Component public class PlaceConverter { + public static PlaceResponseDTO.PlaceAnimationDTO toPlaceAnimationDTO(PlaceAnimation placeAnimation) { + return PlaceResponseDTO.PlaceAnimationDTO.builder() + .placeAnimationId(placeAnimation.getId()) + .animationId(placeAnimation.getAnimation().getId()) + .animationName(placeAnimation.getAnimation().getName()) + .build(); + } + + public static PlaceResponseDTO.PlaceAnimationListDTO toPlaceAnimationListDTO(List placeAnimations) { + return PlaceResponseDTO.PlaceAnimationListDTO.builder() + .placeAnimations(placeAnimations.stream() + .map(PlaceConverter::toPlaceAnimationDTO) + .collect(Collectors.toList())) + .listSize(placeAnimations.size()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java new file mode 100644 index 00000000..88e6912c --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.place.service; + +import com.otakumap.domain.mapping.PlaceAnimation; + +import java.util.List; + +public interface PlaceQueryService { + List getPlaceAnimations(Long placeId); +} diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java new file mode 100644 index 00000000..11a78cd5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -0,0 +1,21 @@ +package com.otakumap.domain.place.service; + +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PlaceQueryServiceImpl implements PlaceQueryService { + private final PlaceAnimationRepository placeAnimationRepository; + + @Override + @Transactional + public List getPlaceAnimations(Long placeId) { + return placeAnimationRepository.findByPlaceId(placeId); + } +} diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index d3f5def1..d988a3ce 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -3,8 +3,10 @@ import com.otakumap.domain.mapping.PlaceAnimation; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface PlaceAnimationRepository extends JpaRepository { Optional findByIdAndPlaceId(Long id, Long placeId); -} + List findByPlaceId(Long placeId); +} \ No newline at end of file From 0899d9cfe550511c2436a32a3d3ab2073713587f Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sat, 1 Feb 2025 22:13:37 +0900 Subject: [PATCH 269/516] =?UTF-8?q?Feat:=20place=20validation=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place/controller/PlaceController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java index bad20a35..7f54f262 100644 --- a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -8,6 +8,7 @@ import com.otakumap.global.validation.annotation.ExistPlace; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -18,6 +19,7 @@ @RestController @RequestMapping("/api/places") @RequiredArgsConstructor +@Validated public class PlaceController { private final PlaceQueryService placeQueryService; From 7ff0eeae4fae8b128ff4587137cd29598f9eed76 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sat, 1 Feb 2025 23:58:21 +0900 Subject: [PATCH 270/516] =?UTF-8?q?Feat:=20RouteItem=EC=9D=98=20itemId?= =?UTF-8?q?=EC=99=80=20itemType=EC=9C=BC=EB=A1=9C=20Route=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 5 --- .../place_review/entity/PlaceReview.java | 5 --- .../reviews/converter/ReviewConverter.java | 9 ++--- .../service/ReviewQueryServiceImpl.java | 33 +++++++++++++++++-- .../domain/route_item/entity/RouteItem.java | 2 +- .../repository/RouteItemRepository.java | 12 +++++++ .../apiPayload/code/status/ErrorStatus.java | 3 ++ .../exception/handler/RouteItemHandler.java | 10 ++++++ 8 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteItemHandler.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index d915b57f..0537d820 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -3,7 +3,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -56,8 +55,4 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_animation_id") private EventAnimation eventAnimation; - - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "route_id", referencedColumnName = "id") - private Route route; } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 5d533438..fe2c27df 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -3,7 +3,6 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -53,8 +52,4 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; - - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "route_id", referencedColumnName = "id") - private Route route; } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 12c2ae2f..ad1a4c42 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -5,6 +5,7 @@ import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.route.converter.RouteConverter; +import com.otakumap.domain.route.entity.Route; import java.util.Objects; @@ -45,7 +46,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .build(); } - public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview) { + public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview, Route route) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(placeReview.getId()) .animationName(placeReview.getPlaceAnimation().getAnimation().getName() != null ? placeReview.getPlaceAnimation().getAnimation().getName() : null) @@ -59,11 +60,11 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi .userName(placeReview.getUser().getName()) .profileImage(ImageConverter.toImageDTO(placeReview.getUser().getProfileImage())) .createdAt(placeReview.getCreatedAt()) - .route(RouteConverter.toRouteDTO(placeReview.getRoute())) + .route(RouteConverter.toRouteDTO(route)) .build(); } - public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview) { + public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview, Route route) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(eventReview.getId()) .animationName(eventReview.getEventAnimation().getAnimation().getName() != null ? eventReview.getEventAnimation().getAnimation().getName() : null) @@ -77,7 +78,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .userName(eventReview.getUser().getName()) .profileImage(ImageConverter.toImageDTO(eventReview.getUser().getProfileImage())) .createdAt(eventReview.getCreatedAt()) - .route(RouteConverter.toRouteDTO(eventReview.getRoute())) + .route(RouteConverter.toRouteDTO(route)) .build(); } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index b84d0ff2..40687ed5 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -8,15 +8,22 @@ import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.enums.ItemType; +import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; +import com.otakumap.global.apiPayload.exception.handler.RouteItemHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -25,6 +32,7 @@ public class ReviewQueryServiceImpl implements ReviewQueryService { private final ReviewRepositoryCustom reviewRepositoryCustom; private final EventReviewRepository eventReviewRepository; private final PlaceReviewRepository placeReviewRepository; + private final RouteItemRepository routeItemRepository; @Override public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { @@ -39,14 +47,35 @@ public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewTy EventReview eventReview = eventReviewRepository.findById(reviewId) .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); - return ReviewConverter.toEventReviewDetailDTO(eventReview); + Route route = getRoute(eventReview.getId(), ItemType.EVENT); + + return ReviewConverter.toEventReviewDetailDTO(eventReview, route); } else if (type == ReviewType.PLACE) { PlaceReview placeReview = placeReviewRepository.findById(reviewId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); - return ReviewConverter.toPlaceReviewDetailDTO(placeReview); + Route route = getRoute(placeReview.getId(), ItemType.PLACE); + + return ReviewConverter.toPlaceReviewDetailDTO(placeReview, route); } else { throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } + + private Route getRoute(Long reviewId, ItemType itemType) { + + List routeItems; + + if (itemType == ItemType.EVENT) { + routeItems = routeItemRepository.findByItemIdAndItemType(reviewId, ItemType.EVENT); + } else { + routeItems = routeItemRepository.findByItemIdAndItemType(reviewId, ItemType.PLACE); + } + + if(routeItems.isEmpty()) { + throw new RouteItemHandler(ErrorStatus.ROUTE_ITEM_NOT_FOUND); + } + + return routeItems.get(0).getRoute(); + } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 8258363f..5981ec50 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -25,7 +25,7 @@ public class RouteItem extends BaseEntity { private ItemType itemType; @Column(nullable = false) - private Long itemId; + private Long itemId; // EventReview 또는 PlaceReview의 id @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "route_id") diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java new file mode 100644 index 00000000..61497ab2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.route_item.repository; + +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.enums.ItemType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface RouteItemRepository extends JpaRepository { + + List findByItemIdAndItemType(Long itemId, ItemType itemType); +} diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index c84c2791..06c8791c 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -66,6 +66,9 @@ public enum ErrorStatus implements BaseErrorCode { ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."), ROUTE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "ROUTE4003", "저장되지 않은 루트입니다."), + // 루트 아이템 관련 에러 + ROUTE_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE_ITEM4001", "존재하지 않는 루트 아이템입니다."), + // 알림 관련 에러 INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION4001", "유효하지 않은 알림 타입입니다."), NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION4002", "존재하지 않는 알림입니다."), diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteItemHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteItemHandler.java new file mode 100644 index 00000000..e623a45f --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/RouteItemHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class RouteItemHandler extends GeneralException { + public RouteItemHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 2640c744133878da512df0f966dcb88adaf5d51e Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 00:01:22 +0900 Subject: [PATCH 271/516] =?UTF-8?q?Refactor:=20Null=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/route/converter/RouteConverter.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index b0a65a3e..bf968377 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -8,10 +8,6 @@ public class RouteConverter { public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { - if(route == null) { - return new RouteResponseDTO.RouteDTO(); - } - return RouteResponseDTO.RouteDTO.builder() .routeId(route.getId()) .routeItems(route.getRouteItems().stream() From 6aff76a45cef44ef20f9187d86e682a5361dc0d4 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 00:05:48 +0900 Subject: [PATCH 272/516] =?UTF-8?q?Feat:=20RouteItem=EC=97=90=20name=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_item/converter/RouteItemConverter.java | 1 + .../otakumap/domain/route_item/dto/RouteItemResponseDTO.java | 1 + .../java/com/otakumap/domain/route_item/entity/RouteItem.java | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index 5f097adc..fcd8f09e 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -8,6 +8,7 @@ public class RouteItemConverter { public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { return RouteItemResponseDTO.RouteItemDTO.builder() .routeItemId(routeItem.getId()) + .name(routeItem.getName()) .itemId(routeItem.getItemId()) .itemType(routeItem.getItemType()) .itemOrder(routeItem.getItemOrder()) diff --git a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java index 1fb1b466..1c0d3c06 100644 --- a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java @@ -14,6 +14,7 @@ public class RouteItemResponseDTO { @AllArgsConstructor public static class RouteItemDTO { Long routeItemId; + String name; Long itemId; ItemType itemType; Integer itemOrder; diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 5981ec50..2a89d873 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -17,6 +17,9 @@ public class RouteItem extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(length = 50, nullable = false) + private String name; + @Column(nullable = false) private Integer itemOrder; From fd67be75692076afee7c2bffa2afd0e3a489c64f Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 01:19:41 +0900 Subject: [PATCH 273/516] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=EB=90=9C=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A4=91=20?= =?UTF-8?q?=ED=95=98=EB=82=98=EB=A7=8C=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/reviews/converter/ReviewConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 453067bc..7f236d89 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -43,7 +43,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr .id(eventReview.getEvent().getId()) .title(eventReview.getTitle()) .content(eventReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(eventReview.getImages().get(0))) // 나중에 수정 + .reviewImage(ImageConverter.toImageDTO(!eventReview.getImages().isEmpty() ? eventReview.getImages().get(0) : null)) // 나중에 수정 .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") @@ -56,7 +56,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .id(placeReview.getPlace().getId()) .title(placeReview.getTitle()) .content(placeReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(placeReview.getImages().get(0))) // 나중에 수정 + .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) // 나중에 수정 .view(placeReview.getView()) .createdAt(placeReview.getCreatedAt()) .type("place") From ebb5fee4b55b5021bc097cacf5ab41abb03f5334 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 01:22:53 +0900 Subject: [PATCH 274/516] =?UTF-8?q?Comment:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/reviews/converter/ReviewConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 7f236d89..5eaea33d 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -43,7 +43,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr .id(eventReview.getEvent().getId()) .title(eventReview.getTitle()) .content(eventReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(!eventReview.getImages().isEmpty() ? eventReview.getImages().get(0) : null)) // 나중에 수정 + .reviewImage(ImageConverter.toImageDTO(!eventReview.getImages().isEmpty() ? eventReview.getImages().get(0) : null)) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") @@ -56,7 +56,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .id(placeReview.getPlace().getId()) .title(placeReview.getTitle()) .content(placeReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) // 나중에 수정 + .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) .view(placeReview.getView()) .createdAt(placeReview.getCreatedAt()) .type("place") From 63d407f70d51a83ab31f05a1c0f345ae4b1ccf1c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 2 Feb 2025 05:20:39 +0900 Subject: [PATCH 275/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 10 ++- .../place_review/entity/PlaceReview.java | 8 +- .../controller/ReviewSearchController.java | 23 +++++- .../reviews/converter/ReviewConverter.java | 65 ++++++++++++---- .../domain/reviews/dto/ReviewRequestDTO.java | 44 +++++++++++ .../domain/reviews/dto/ReviewResponseDTO.java | 10 +++ .../reviews/service/ReviewCommandService.java | 10 +++ .../service/ReviewCommandServiceImpl.java | 75 +++++++++++++++++++ .../domain/user/converter/UserConverter.java | 2 +- 9 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 2ed19347..d3675936 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -9,6 +9,8 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.List; + @Entity @Getter @DynamicUpdate @@ -33,9 +35,9 @@ public class EventReview extends BaseEntity { @Column(nullable = false) private Float rating; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "image_id", referencedColumnName = "id") - private Image image; + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "event_review_id") + private List images; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") @@ -44,4 +46,4 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; -} +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 99cc122d..e6aac978 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -10,6 +10,8 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.List; + @Entity @Getter @DynamicInsert @@ -31,9 +33,9 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "image_id", referencedColumnName = "id") - private Image image; + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "place_review_id") + private List images; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java index 858ca905..5829f916 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewSearchController.java @@ -1,18 +1,23 @@ package com.otakumap.domain.reviews.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.service.ReviewCommandService; import com.otakumap.domain.reviews.service.ReviewQueryService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; 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.responses.ApiResponses; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -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.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -20,6 +25,7 @@ public class ReviewSearchController { private final ReviewQueryService reviewQueryService; + private final ReviewCommandService reviewCommandService; @GetMapping("/reviews/top7") @Operation(summary = "조회수 Top7 여행 후기 목록 조회", description = "조회수 Top7 여행 후기 목록을 조회합니다.") @@ -51,4 +57,13 @@ public ApiResponse> getSearched return ApiResponse.onSuccess(searchResults); } + + @PostMapping(name = "/reviews", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다.") + public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) + @RequestPart("request") @Valid ReviewRequestDTO.CreateDTO request, + @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { + ReviewResponseDTO.createdReviewDTO createdReview = reviewCommandService.createReview(request, user, images); + return ApiResponse.onSuccess(createdReview); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index be741f22..ce2e94b6 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,9 +1,18 @@ package com.otakumap.domain.reviews.converter; +import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.user.entity.User; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; public class ReviewConverter { @@ -11,24 +20,22 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(eventReview.getImage() != null ? - ImageConverter.toImageDTO(eventReview.getImage()) : - null) + .reviewImage(eventReview.getImages().isEmpty() ? null : + ImageConverter.toImageDTO(eventReview.getImages().get(0))) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") .build(); } - public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO(PlaceReview eventReview) { + public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO(PlaceReview placeReview) { return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() - .id(eventReview.getId()) - .title(eventReview.getTitle()) - .reviewImage(eventReview.getImage() != null ? - ImageConverter.toImageDTO(eventReview.getImage()) : - null) - .view(eventReview.getView()) - .createdAt(eventReview.getCreatedAt()) + .id(placeReview.getId()) + .title(placeReview.getTitle()) + .reviewImage(placeReview.getImages().isEmpty() ? null : + ImageConverter.toImageDTO(placeReview.getImages().get(0))) + .view(placeReview.getView()) + .createdAt(placeReview.getCreatedAt()) .type("place") .build(); } @@ -39,7 +46,8 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr .id(eventReview.getEvent().getId()) .title(eventReview.getTitle()) .content(eventReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(eventReview.getImage())) + .reviewImage(eventReview.getImages().isEmpty() ? null : + ImageConverter.toImageDTO(eventReview.getImages().get(0))) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") @@ -52,10 +60,39 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .id(placeReview.getPlace().getId()) .title(placeReview.getTitle()) .content(placeReview.getContent()) - .reviewImage(ImageConverter.toImageDTO(placeReview.getImage())) + .reviewImage(placeReview.getImages().isEmpty() ? null : + ImageConverter.toImageDTO(placeReview.getImages().get(0))) .view(placeReview.getView()) .createdAt(placeReview.getCreatedAt()) .type("place") .build(); } -} + + public static ReviewResponseDTO.createdReviewDTO toCreatedReviewDTO(Long reviewId, String title) { + return ReviewResponseDTO.createdReviewDTO.builder() + .reviewId(reviewId) + .title(title) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, Event event) { + return EventReview.builder() + .title(request.getTitle()) + .content(request.getContent()) + .view(0L) + .user(user) + .event(event) + .build(); + } + + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Place place) { + return PlaceReview.builder() + .title(request.getTitle()) + .content(request.getContent()) + .view(0L) + .user(user) + .place(place) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java new file mode 100644 index 00000000..78522aca --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -0,0 +1,44 @@ +package com.otakumap.domain.reviews.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +import java.util.List; + +public class ReviewRequestDTO { + @Getter + public static class CreateDTO { + @NotBlank(message = "제목을 입력해주세요.") + private String title; + + @NotBlank(message = "내용을 입력해주세요.") + private String content; + + private Long eventId; + private Long placeId; + + @NotNull(message = "애니메이션 id를 입력해주세요.") + private Long animeId; + + @Size(min = 1, message = "루트 아이템은 최소 1개 이상 필요합니다.") + private List routeItems; + } + + @Getter + public static class RouteDTO { + @NotNull(message = "itemId를 입력해주세요.") + private Long itemId; + + @NotNull(message = "itemType을 입력해주세요.") + private ItemType itemType; + + @NotNull(message = "order를 입력해주세요.") + private Integer order; + } + + public enum ItemType { + PLACE, EVENT + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 15d3adb9..a8dfa18e 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -46,4 +46,14 @@ public static class SearchedReviewPreViewDTO { LocalDateTime createdAt; ImageResponseDTO.ImageDTO reviewImage; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class createdReviewDTO { + Long reviewId; + String title; + LocalDateTime createdAt; + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java new file mode 100644 index 00000000..93df06ed --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.reviews.service; + +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.user.entity.User; +import org.springframework.web.multipart.MultipartFile; + +public interface ReviewCommandService { + ReviewResponseDTO.createdReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images); +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java new file mode 100644 index 00000000..c3bc5db8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -0,0 +1,75 @@ +package com.otakumap.domain.reviews.service; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.repository.AnimationRepository; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.domain.image.service.ImageCommandService; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.reviews.converter.ReviewConverter; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ReviewCommandServiceImpl implements ReviewCommandService { + private final PlaceReviewRepository placeReviewRepository; + private final EventReviewRepository eventReviewRepository; + private final AnimationRepository animationRepository; + private final PlaceRepository placeRepository; + private final EventRepository eventRepository; + private final PlaceAnimationRepository placeAnimationRepository; + private final RouteRepository routeRepository; + private final ImageCommandService imageCommandService; + + @Override + @Transactional + public ReviewResponseDTO.createdReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images) { + if (request.getPlaceId() != null && request.getEventId() != null) { + throw new IllegalArgumentException("이벤트 후기와 장소 후기 중 하나만 선택해주세요."); + } else if (request.getPlaceId() == null && request.getEventId() == null) { + throw new IllegalArgumentException("이벤트 후기 또는 장소 후기 중 하나를 선택해주세요."); + } + + animationRepository.findById(request.getAnimeId()) + .orElseThrow(() -> new AnimationHandler(ErrorStatus.ANIMATION_NOT_FOUND)); + + List reviewImages = List.of(images); + + if (request.getPlaceId() != null) { // place review + Place place = placeRepository.findById(request.getPlaceId()) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + PlaceReview placeReview = placeReviewRepository.save(ReviewConverter.toPlaceReview(request, user, place)); + imageCommandService.uploadReviewImages(reviewImages, placeReview.getId()); + return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); + } + + else { // event review + Event event = eventRepository.findById(request.getEventId()) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + + EventReview eventReview = eventReviewRepository.save(ReviewConverter.toEventReview(request, user, event)); + imageCommandService.uploadReviewImages(reviewImages, eventReview.getId()); + return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); + } + } +} diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 52828de3..f9f04544 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -111,7 +111,7 @@ public static UserResponseDTO.UserReviewDTO reviewDTO(PlaceReview review) { .reviewId(review.getId()) .title(review.getTitle()) .content(review.getContent()) - .thumbnail(review.getImage() == null ? null : review.getImage().getFileUrl()) // 이미지 여러 개면 수정 + .thumbnail(review.getImages() == null ? null : review.getImages().indexOf(0).getFileUrl()) // 이미지 여러 개면 수정 .views(review.getView()) .createdAt(review.getCreatedAt().toLocalDate()) .build(); From ab5fcc2631b7a7a6c005f827e7580f56c0d8cab1 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sun, 2 Feb 2025 06:21:54 +0900 Subject: [PATCH 276/516] =?UTF-8?q?Fix:=20path=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/controller/ReviewController.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index e6821168..61ea5971 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -1,5 +1,7 @@ package com.otakumap.domain.reviews.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.reviews.service.ReviewCommandService; @@ -18,7 +20,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @RestController @@ -28,6 +29,7 @@ public class ReviewController { private final ReviewQueryService reviewQueryService; + private final ReviewCommandService reviewCommandService; @GetMapping("/reviews/top7") @Operation(summary = "조회수 Top7 여행 후기 목록 조회", description = "조회수 Top7 여행 후기 목록을 조회합니다.") @@ -74,11 +76,11 @@ public ApiResponse getReviewDetail(@PathVaria return ApiResponse.onSuccess(reviewQueryService.getReviewDetail(reviewId, type)); } - @PostMapping(name = "/reviews", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) - @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다.") + @PostMapping(value = "/reviews", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다. 장소, 이벤트 후기 중 하나만 작성할 수 있으며, 최소 1개 이상의 루트 아이템이 필요합니다.") public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) @RequestPart("request") @Valid ReviewRequestDTO.CreateDTO request, - @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { + @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { ReviewResponseDTO.createdReviewDTO createdReview = reviewCommandService.createReview(request, user, images); return ApiResponse.onSuccess(createdReview); } From 414800fa2677c9b03b780a6d78f0767114ac6765 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:27:36 +0900 Subject: [PATCH 277/516] =?UTF-8?q?Remove:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/service/GooglePlacesService.java | 38 ---------- .../controller/ReviewController 2.java | 69 ------------------- .../dto/RouteItemResponseDTO 2.java | 22 ------ 3 files changed, 129 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java delete mode 100644 src/main/java/com/otakumap/domain/reviews/controller/ReviewController 2.java delete mode 100644 src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO 2.java diff --git a/src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java b/src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java deleted file mode 100644 index 08260962..00000000 --- a/src/main/java/com/otakumap/domain/place/service/GooglePlacesService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.otakumap.domain.place.service; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.otakumap.domain.place.dto.PlaceResponseDTO; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -@Service -@RequiredArgsConstructor -public class GooglePlacesService { - - @Value("${google.api.key}") - private String apiKey; - - private final RestTemplate restTemplate; - private final ObjectMapper objectMapper; - - public PlaceResponseDTO searchPlaces(String query) { - String url = String.format( - "https://maps.googleapis.com/maps/api/place/textsearch/json?query=%s&key=%s", - query, - apiKey - ); - - // API 호출 - String response = restTemplate.getForObject(url, String.class); - System.out.println(response); // 실제 API 응답 확인 - - try { - // 응답 데이터를 DTO로 변환 - return objectMapper.readValue(response, PlaceResponseDTO.class); - } catch (Exception e) { - throw new RuntimeException("Failed to parse Google Places API response", e); - } - } -} diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController 2.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController 2.java deleted file mode 100644 index b1fad52d..00000000 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController 2.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.otakumap.domain.reviews.controller; - -import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import com.otakumap.domain.reviews.enums.ReviewType; -import com.otakumap.domain.reviews.service.ReviewQueryService; -import com.otakumap.global.apiPayload.ApiResponse; -import com.otakumap.global.validation.annotation.ValidReviewId; -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.responses.ApiResponses; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api") -@Validated -public class ReviewController { - - private final ReviewQueryService reviewQueryService; - - @GetMapping("/reviews/top7") - @Operation(summary = "조회수 Top7 여행 후기 목록 조회", description = "조회수 Top7 여행 후기 목록을 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), - }) - public ApiResponse getTop7ReviewList() { - ReviewResponseDTO.Top7ReviewPreViewListDTO top7Reviews = reviewQueryService.getTop7Reviews(); - return ApiResponse.onSuccess(top7Reviews); - } - - @GetMapping("/reviews/search") - @Operation(summary = "키워드로 여행 후기 검색", description = "키워드로 여행 후기를 검색해서 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), - }) - @Parameters({ - @Parameter(name = "keyword", description = "검색 키워드입니다."), - @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), - @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), - @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") - }) - public ApiResponse> getSearchedReviewList(@RequestParam String keyword, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "latest") String sort) { - - Page searchResults = reviewQueryService.searchReviewsByKeyword(keyword, page, size, sort); - - return ApiResponse.onSuccess(searchResults); - } - - @GetMapping("/reviews/{reviewId}") - @Operation(summary = "특정 여행 후기 조회", description = "특정 여행 후기를 상세 페이지에서 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), - }) - @Parameters({ - @Parameter(name = "reviewId", description = "이벤트 or 명소의 후기 id 입니다."), - @Parameter(name = "type", description = "리뷰의 종류를 특정합니다. 'EVENT' 또는 'PLACE' 여야 합니다.") - }) - public ApiResponse getReviewDetail(@PathVariable @ValidReviewId Long reviewId, @RequestParam(defaultValue = "PLACE") ReviewType type) { - - return ApiResponse.onSuccess(reviewQueryService.getReviewDetail(reviewId, type)); - } -} diff --git a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO 2.java b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO 2.java deleted file mode 100644 index 1c0d3c06..00000000 --- a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO 2.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.otakumap.domain.route_item.dto; - -import com.otakumap.domain.route_item.enums.ItemType; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -public class RouteItemResponseDTO { - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class RouteItemDTO { - Long routeItemId; - String name; - Long itemId; - ItemType itemType; - Integer itemOrder; - } -} From a40944cde8dab831ab610d76383fce7a3b9ddd21 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:28:43 +0900 Subject: [PATCH 278/516] =?UTF-8?q?Refactor:=20PlaceController=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=97=86=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/controller/PlaceController.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java index d810ba22..2ac76bad 100644 --- a/src/main/java/com/otakumap/domain/place/controller/PlaceController.java +++ b/src/main/java/com/otakumap/domain/place/controller/PlaceController.java @@ -1,11 +1,7 @@ package com.otakumap.domain.place.controller; -import com.otakumap.domain.place.dto.PlaceResponseDTO; -import com.otakumap.domain.place.service.GooglePlacesService; import lombok.RequiredArgsConstructor; -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; @RestController @@ -13,12 +9,4 @@ @RequiredArgsConstructor public class PlaceController { - private final GooglePlacesService googlePlacesService; - - - @GetMapping("/places/search") - public PlaceResponseDTO searchPlaces(@RequestParam String query) { - return googlePlacesService.searchPlaces(query); - } - -} +} \ No newline at end of file From fd3ce2e95f8a7a657fda001bf15fdfe9be5292fb Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:31:06 +0900 Subject: [PATCH 279/516] =?UTF-8?q?Remove:=20PlaceResponseDTO=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/dto/PlaceResponseDTO.java | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java deleted file mode 100644 index 8c45fc08..00000000 --- a/src/main/java/com/otakumap/domain/place/dto/PlaceResponseDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.otakumap.domain.place.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.*; - -import java.util.List; - -@Data -public class PlaceResponseDTO { - - @JsonProperty("results") - private List results; - - @Data - public static class SearchedPlaceDTO { - - @JsonProperty("formatted_address") - private String formattedAddress; - - @JsonProperty("geometry") - private Geometry geometry; - - @JsonProperty("name") - private String name; - - @Data - public static class Geometry { - @JsonProperty("location") - private Location location; - - @Data - public static class Location { - private double lat; - private double lng; - } - } - - } -} From 49e8796a44d5fa615466e94861f0dfd9b05783d8 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:33:34 +0900 Subject: [PATCH 280/516] =?UTF-8?q?Feat:=20EventLocation=20=EC=9C=84?= =?UTF-8?q?=EB=8F=84/=EA=B2=BD=EB=8F=84=20=ED=83=80=EC=9E=85=20Double?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_location/converter/EventLocationConverter.java | 4 ++-- .../event_location/dto/EventLocationResponseDTO.java | 4 ++-- .../domain/event_location/entity/EventLocation.java | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java b/src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java index f73baa61..cba1ef2b 100644 --- a/src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java +++ b/src/main/java/com/otakumap/domain/event_location/converter/EventLocationConverter.java @@ -10,8 +10,8 @@ public static EventLocationResponseDTO.EventLocationDTO toEventLocationDTO(Event return EventLocationResponseDTO.EventLocationDTO.builder() .id(eventLocation.getId()) .name(eventLocation.getName()) - .latitude(eventLocation.getLatitude()) - .longitude(eventLocation.getLongitude()) + .latitude(eventLocation.getLat()) + .longitude(eventLocation.getLng()) .build(); } } diff --git a/src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java b/src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java index c6c48514..f876226f 100644 --- a/src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_location/dto/EventLocationResponseDTO.java @@ -14,7 +14,7 @@ public class EventLocationResponseDTO { public static class EventLocationDTO { Long id; String name; - String longitude; - String latitude; + Double longitude; + Double latitude; } } diff --git a/src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java b/src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java index 30a7dd28..cfd62a56 100644 --- a/src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java +++ b/src/main/java/com/otakumap/domain/event_location/entity/EventLocation.java @@ -19,11 +19,11 @@ public class EventLocation extends BaseEntity { @Column(columnDefinition = "VARCHAR(50)", nullable = false) private String name; - @Column(columnDefinition = "TEXT") - private String latitude; + @Column(nullable = false) + private Double lat; - @Column(columnDefinition = "TEXT") - private String longitude; + @Column(nullable = false) + private Double lng; @OneToOne(mappedBy = "eventLocation", fetch = FetchType.LAZY) private Event event; From a70378ac1e0e3a2a773cd1c63ff06fe0e572e6e3 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:36:15 +0900 Subject: [PATCH 281/516] =?UTF-8?q?Feat:=20SearchedEventInfoDTO=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/converter/EventConverter.java | 17 +++++++++++++++++ .../domain/event/dto/EventResponseDTO.java | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 2cdf6ac5..04aebc5d 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -3,8 +3,13 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_location.converter.EventLocationConverter; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.entity.HashTag; import com.otakumap.domain.image.converter.ImageConverter; +import java.util.List; +import java.util.stream.Collectors; + public class EventConverter { public static EventResponseDTO.EventDTO toEventDTO(Event event) { @@ -32,4 +37,16 @@ public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { .eventLocation(EventLocationConverter.toEventLocationDTO(event.getEventLocation())) .build(); } + + public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event event, Boolean isFavorite, List hashTags) { + + return EventResponseDTO.SearchedEventInfoDTO.builder() + .eventId(event.getId()) + .name(event.getName()) + .isFavorite(isFavorite) + .hashTags(hashTags.stream() + .map(HashTagConverter::toHashTagDTO) + .collect(Collectors.toList())) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index c0da1f19..f44ddcfc 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -1,6 +1,7 @@ package com.otakumap.domain.event.dto; import com.otakumap.domain.event_location.dto.EventLocationResponseDTO; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -8,6 +9,7 @@ import lombok.NoArgsConstructor; import java.time.LocalDate; +import java.util.List; public class EventResponseDTO { @@ -40,4 +42,15 @@ public static class EventDetailDTO { ImageResponseDTO.ImageDTO goodsImage; EventLocationResponseDTO.EventLocationDTO eventLocation; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchedEventInfoDTO { + Long eventId; + String name; + Boolean isFavorite; + List hashTags; + } } From 2dc5c24dd816d854459def2fb6f61984138fe50c Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:37:18 +0900 Subject: [PATCH 282/516] =?UTF-8?q?Feat:=20EventLikeRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/repository/EventLikeRepository.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java index bff93808..46c8abdf 100644 --- a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java +++ b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java @@ -1,8 +1,13 @@ package com.otakumap.domain.event_like.repository; -import com.otakumap.domain.event_like.entity.EventLike;; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +; + public interface EventLikeRepository extends JpaRepository { + EventLike findByUserAndEvent(User user, Event event); } From bd8fb2f39fb9817027f3d6eef57411ea85e8e363 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:38:32 +0900 Subject: [PATCH 283/516] =?UTF-8?q?Feat=20EventLocationRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventLocationRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java diff --git a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java new file mode 100644 index 00000000..93ae5ca8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.event_location.repository; + +import com.otakumap.domain.event_location.entity.EventLocation; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface EventLocationRepository extends JpaRepository { + List findByLatAndLng(Double latitude, Double longitude); +} From 9d78688e48149c2d1b0b35386c5c7c52e9c75506 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:39:17 +0900 Subject: [PATCH 284/516] =?UTF-8?q?Feat:=20EventHashTagRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapping/repository/EventHashTagRepository.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java diff --git a/src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java b/src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java new file mode 100644 index 00000000..83ce91fe --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.mapping.repository; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.mapping.EventHashTag; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface EventHashTagRepository extends JpaRepository { + List findByEvent(Event event); +} From e6be7e7d830942c4a09850217deeb9650ba01027 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:42:22 +0900 Subject: [PATCH 285/516] =?UTF-8?q?Remove:=20GoogleMapsProperties=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/GoogleMapsProperties.java | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/main/java/com/otakumap/global/config/GoogleMapsProperties.java diff --git a/src/main/java/com/otakumap/global/config/GoogleMapsProperties.java b/src/main/java/com/otakumap/global/config/GoogleMapsProperties.java deleted file mode 100644 index 375307ca..00000000 --- a/src/main/java/com/otakumap/global/config/GoogleMapsProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.otakumap.global.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -@Data -@Component -@ConfigurationProperties(prefix = "google") -public class GoogleMapsProperties { - private GoogleMapsProperties.ProviderProperties api; - - @Data - public static class ProviderProperties { - private String key; - } -} From e9a365f6f6aef59f529e53e85307a55c807429ed Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Sun, 2 Feb 2025 08:43:21 +0900 Subject: [PATCH 286/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8/?= =?UTF-8?q?=EC=9E=91=ED=92=88=EB=AA=85=20=EC=A7=80=EB=8F=84=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/controller/SearchController.java | 39 ++++++++ .../search/converter/SearchConverter.java | 18 ++++ .../domain/search/dto/SearchResponseDTO.java | 23 +++++ .../repository/SearchRepositoryCustom.java | 11 +++ .../SearchRepositoryCustomImpl.java | 61 ++++++++++++ .../domain/search/service/SearchService.java | 11 +++ .../search/service/SearchServiceImpl.java | 98 +++++++++++++++++++ 7 files changed, 261 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/search/controller/SearchController.java create mode 100644 src/main/java/com/otakumap/domain/search/converter/SearchConverter.java create mode 100644 src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java create mode 100644 src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java create mode 100644 src/main/java/com/otakumap/domain/search/service/SearchService.java create mode 100644 src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/search/controller/SearchController.java b/src/main/java/com/otakumap/domain/search/controller/SearchController.java new file mode 100644 index 00000000..cb461e5c --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/controller/SearchController.java @@ -0,0 +1,39 @@ +package com.otakumap.domain.search.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.search.dto.SearchResponseDTO; +import com.otakumap.domain.search.service.SearchService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +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.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class SearchController { + + private final SearchService searchService; + + @GetMapping("/map/search") + @Operation(summary = "이벤트/작품명 지도 검색", description = "키워드로 이벤트/장소명/애니메이션 제목을 검색하여 관련된 장소 위치와 그 위치의 이벤트를 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "keyword", description = "검색 키워드입니다."), + }) + public ApiResponse> getSearchedPlaceInfoList(@CurrentUser User user, @RequestParam String keyword) { + + return ApiResponse.onSuccess(searchService.getSearchedResult(user, keyword)); + } +} diff --git a/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java b/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java new file mode 100644 index 00000000..c9169f0c --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java @@ -0,0 +1,18 @@ +package com.otakumap.domain.search.converter; + +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.search.dto.SearchResponseDTO; + +import java.util.List; + +public class SearchConverter { + + public static SearchResponseDTO.SearchResultDTO toSearchResultDTO(List eventDTOs, Double lat, Double lng) { + return SearchResponseDTO.SearchResultDTO.builder() + .latitude(lat) + .longitude(lng) + .eventCount(eventDTOs.size()) + .events(eventDTOs) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java b/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java new file mode 100644 index 00000000..94ec865b --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java @@ -0,0 +1,23 @@ +package com.otakumap.domain.search.dto; + +import com.otakumap.domain.event.dto.EventResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class SearchResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchResultDTO { + private Double latitude; + private Double longitude; + private int eventCount; + private List events; + } +} diff --git a/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java new file mode 100644 index 00000000..2a2d53c2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.search.repository; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.place.entity.Place; + +import java.util.List; + +public interface SearchRepositoryCustom { + List searchEventsByKeyword(String keyword); + List searchPlacesByKeyword(String keyword); +} diff --git a/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java new file mode 100644 index 00000000..bb60f601 --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java @@ -0,0 +1,61 @@ +package com.otakumap.domain.search.repository; + + +import com.otakumap.domain.animation.entity.QAnimation; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.QEvent; +import com.otakumap.domain.event.entity.enums.EventStatus; +import com.otakumap.domain.mapping.QEventAnimation; +import com.otakumap.domain.mapping.QPlaceAnimation; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.entity.QPlace; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class SearchRepositoryCustomImpl implements SearchRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List searchEventsByKeyword(String keyword) { + + QEvent qEvent = QEvent.event; + QEventAnimation qEventAnimation = QEventAnimation.eventAnimation; + QAnimation qAnimation = QAnimation.animation; + + BooleanBuilder condition = new BooleanBuilder(); + condition.or(qEvent.title.containsIgnoreCase(keyword)) + .or(qAnimation.name.containsIgnoreCase(keyword)) + .and(qEvent.status.ne(EventStatus.ENDED)); + + return queryFactory.selectFrom(qEvent) + .leftJoin(qEventAnimation).on(qEventAnimation.event.eq(qEvent)) + .leftJoin(qAnimation).on(qEventAnimation.animation.eq(qAnimation)) + .where(condition) + .fetch(); + } + + @Override + public List searchPlacesByKeyword(String keyword) { + + QPlace qPlace = QPlace.place; + QPlaceAnimation qPlaceAnimation = QPlaceAnimation.placeAnimation; + QAnimation qAnimation = QAnimation.animation; + + BooleanBuilder condition = new BooleanBuilder(); + condition.or(qPlace.name.containsIgnoreCase(keyword)) + .or(qAnimation.name.containsIgnoreCase(keyword)); + + return queryFactory.selectFrom(qPlace) + .leftJoin(qPlaceAnimation).on(qPlaceAnimation.place.eq(qPlace)) + .leftJoin(qAnimation).on(qPlaceAnimation.animation.eq(qAnimation)) + .where(condition) + .fetch(); + } +} diff --git a/src/main/java/com/otakumap/domain/search/service/SearchService.java b/src/main/java/com/otakumap/domain/search/service/SearchService.java new file mode 100644 index 00000000..8bd08c69 --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/service/SearchService.java @@ -0,0 +1,11 @@ +package com.otakumap.domain.search.service; + +import com.otakumap.domain.search.dto.SearchResponseDTO; +import com.otakumap.domain.user.entity.User; + +import java.util.List; + +public interface SearchService { + + List getSearchedResult (User user, String keyword); +} diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java new file mode 100644 index 00000000..e1646520 --- /dev/null +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -0,0 +1,98 @@ +package com.otakumap.domain.search.service; + +import com.otakumap.domain.event.converter.EventConverter; +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.enums.EventStatus; +import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.event_like.repository.EventLikeRepository; +import com.otakumap.domain.event_location.entity.EventLocation; +import com.otakumap.domain.event_location.repository.EventLocationRepository; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.mapping.EventHashTag; +import com.otakumap.domain.mapping.repository.EventHashTagRepository; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.search.converter.SearchConverter; +import com.otakumap.domain.search.dto.SearchResponseDTO; +import com.otakumap.domain.search.repository.SearchRepositoryCustom; +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +@RequiredArgsConstructor +public class SearchServiceImpl implements SearchService { + + private final SearchRepositoryCustom searchRepository; + private final EventLocationRepository eventLocationRepository; + private final EventLikeRepository eventLikeRepository; + private final EventHashTagRepository eventHashTagRepository; + + @Transactional(readOnly = true) + @Override + public List getSearchedResult (User user, String keyword) { + + List events = searchRepository.searchEventsByKeyword(keyword); + List places = searchRepository.searchPlacesByKeyword(keyword); + + List safePlaces = places != null ? places : Collections.emptyList(); // 불변 + List safeEvents = events != null ? events : new ArrayList<>(); // 가변 + + // 검색한 결과에 장소가 있을 때 -> 각 장소의 위/경도와 같은 곳에서 진행되고 있는 event를 찾음 + List newEvents = safePlaces.stream() + .flatMap(place -> eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()).stream()) + .map(EventLocation::getEvent) + .filter(event -> event != null && event.getStatus() != EventStatus.ENDED) + .toList(); + + // 기존에 검색된 이벤트와 합치기 + List combinedEvents = + Stream.concat(safeEvents.stream(), newEvents.stream()) + .distinct() // 중복 이벤트 제거 + .toList(); + + // 위도,경도 조합으로 같은 것들끼리 그룹화 + Map> groupedEvents = combinedEvents.stream() + .filter(event -> event.getEventLocation() != null) + .collect(Collectors.groupingBy(event -> { + Double lat = event.getEventLocation().getLat(); + Double lng = event.getEventLocation().getLng(); + return lat + "," + lng; + })); + + return groupedEvents.entrySet().stream() + .map(entry -> { + String key = entry.getKey(); // "lat,lng" 형태의 키 값에서 위/경도 추출 + String[] parts = key.split(","); + Double lat = Double.valueOf(parts[0]); + Double lng = Double.valueOf(parts[1]); + + // 각 event를 DTO로 변환 + List eventDTOs = entry.getValue().stream() + .map(event -> { + EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); + boolean isFavorite = eventLike != null && eventLike.getIsFavorite() == Boolean.TRUE; + + List eventHashTags = eventHashTagRepository.findByEvent(event); + List hashTags = eventHashTags.stream() + .map(EventHashTag::getHashTag) + .collect(Collectors.toList()); + + return EventConverter.toSearchedEventInfoDTO(event, isFavorite, hashTags); + }) + .collect(Collectors.toList()); + + return SearchConverter.toSearchResultDTO(eventDTOs, lat, lng); + }) + .toList(); + } + +} From c60eb699669e1080a6caa96133e8fc3513e2e2af Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 2 Feb 2025 15:03:42 +0900 Subject: [PATCH 287/516] =?UTF-8?q?Feat:=20PlaceLike=EC=9D=98=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=EB=A5=BC=20ExistPlaceLi?= =?UTF-8?q?ke,=20ExistPlaceListLike=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validation/annotation/ExistPlaceLike.java | 2 +- .../annotation/ExistPlaceListLike.java | 17 +++++++++ .../validator/PlaceLikeExistValidator.java | 10 ++--- .../PlaceListLikeExistValidator.java | 38 +++++++++++++++++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java index a81cd186..eab73031 100644 --- a/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceLike.java @@ -11,7 +11,7 @@ @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface ExistPlaceLike { - String message() default "유효하지 않은 명소 ID가 포함되어 있습니다."; + String message() default "유효하지 않은 명소 ID 입니다."; Class[] groups() default {}; Class[] payload() default {}; } diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java new file mode 100644 index 00000000..6b736737 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.PlaceListLikeExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = PlaceListLikeExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistPlaceListLike { + String message() default "유효하지 않은 명소 ID가 포함되어 있습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java index 86fd7129..4af650df 100644 --- a/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java +++ b/src/main/java/com/otakumap/global/validation/validator/PlaceLikeExistValidator.java @@ -8,11 +8,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import java.util.List; @Component @RequiredArgsConstructor -public class PlaceLikeExistValidator implements ConstraintValidator> { +public class PlaceLikeExistValidator implements ConstraintValidator { private final PlaceLikeQueryService placeLikeQueryService; @Override @@ -21,13 +20,12 @@ public void initialize(ExistPlaceLike constraintAnnotation) { } @Override - public boolean isValid(List placeIds, ConstraintValidatorContext context) { - if (placeIds == null || placeIds.isEmpty()) { + public boolean isValid(Long placeLikeId, ConstraintValidatorContext context) { + if (placeLikeId == null ) { return false; } - boolean isValid = placeIds.stream() - .allMatch(placeId -> placeLikeQueryService.isPlaceLikeExist(placeId)); + boolean isValid = placeLikeQueryService.isPlaceLikeExist(placeLikeId); if (!isValid) { context.disableDefaultConstraintViolation(); diff --git a/src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java new file mode 100644 index 00000000..87443f81 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java @@ -0,0 +1,38 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.place_like.service.PlaceLikeQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistPlaceListLike; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class PlaceListLikeExistValidator implements ConstraintValidator> { + private final PlaceLikeQueryService placeLikeQueryService; + + @Override + public void initialize(ExistPlaceListLike constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List placeIds, ConstraintValidatorContext context) { + if (placeIds == null || placeIds.isEmpty()) { + return false; + } + + boolean isValid = placeIds.stream() + .allMatch(placeId -> placeLikeQueryService.isPlaceLikeExist(placeId)); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.PLACE_LIKE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} From 7ecf4571f5d234aecdb305eda78e64b3e595dcb5 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 2 Feb 2025 15:13:57 +0900 Subject: [PATCH 288/516] =?UTF-8?q?Refactor:=20place=5Fhashtag=EB=A5=BC=20?= =?UTF-8?q?place=5Fanimation=5Fhashtag=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/hash_tag/entity/HashTag.java | 9 ++++++++- .../com/otakumap/domain/mapping/PlaceAnimation.java | 10 ++++++++++ .../{PlaceHashTag.java => PlaceAnimationHashTag.java} | 8 +++----- .../java/com/otakumap/domain/place/entity/Place.java | 5 ----- 4 files changed, 21 insertions(+), 11 deletions(-) rename src/main/java/com/otakumap/domain/mapping/{PlaceHashTag.java => PlaceAnimationHashTag.java} (76%) diff --git a/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java b/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java index b70b56c0..865fa316 100644 --- a/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java +++ b/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java @@ -1,9 +1,13 @@ package com.otakumap.domain.hash_tag.entity; +import com.otakumap.domain.mapping.PlaceAnimationHashTag; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @@ -17,4 +21,7 @@ public class HashTag extends BaseEntity { @Column(nullable = false, length = 20) private String name; -} + + @OneToMany(mappedBy = "hashTag", cascade = CascadeType.ALL) + private List placeAnimationHashTags = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java index 6372a340..f2b4b6ce 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java +++ b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java @@ -2,10 +2,14 @@ import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @@ -24,4 +28,10 @@ public class PlaceAnimation extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "animation_id") private Animation animation; + + @OneToMany(mappedBy = "placeAnimation", cascade = CascadeType.ALL) + private List placeAnimationHashTags = new ArrayList<>(); + + @OneToMany(mappedBy = "placeAnimation", cascade = CascadeType.ALL) + private List placeLikes = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java b/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java similarity index 76% rename from src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java rename to src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java index eb6f5ffd..7c843935 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceHashTag.java +++ b/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java @@ -1,7 +1,6 @@ package com.otakumap.domain.mapping; import com.otakumap.domain.hash_tag.entity.HashTag; -import com.otakumap.domain.place.entity.Place; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -11,15 +10,14 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class PlaceHashTag extends BaseEntity { - +public class PlaceAnimationHashTag extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") - private Place place; + @JoinColumn(name = "place_animation_id") + private PlaceAnimation placeAnimation; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "hash_tag_id") diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 26d365a8..c28e5fb4 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,7 +1,6 @@ package com.otakumap.domain.place.entity; import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.PlaceHashTag; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -44,8 +43,4 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); - - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) - private List placeHashTagList = new ArrayList<>(); - } From 90bc2a8d7b1c95da8ad92a25d8c1189efb7a90f7 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 2 Feb 2025 15:17:49 +0900 Subject: [PATCH 289/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=8B=9C=20=EC=84=A0=ED=83=9D=ED=95=9C=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceAnimationRepository.java | 1 + .../controller/PlaceLikeController.java | 17 ++++++++++++----- .../place_like/dto/PlaceLikeRequestDTO.java | 6 ++++++ .../domain/place_like/entity/PlaceLike.java | 8 +++----- .../repository/PlaceLikeRepository.java | 10 ++-------- .../service/PlaceLikeCommandService.java | 4 +--- .../service/PlaceLikeCommandServiceImpl.java | 16 +++++++--------- 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index d3f5def1..51af6d31 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -7,4 +7,5 @@ public interface PlaceAnimationRepository extends JpaRepository { Optional findByIdAndPlaceId(Long id, Long placeId); + Optional findByPlaceIdAndAnimationId(Long placeId, Long animationId); } diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 996f7427..c0db6068 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -8,7 +8,10 @@ import com.otakumap.domain.place_like.service.PlaceLikeQueryService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; + +import com.otakumap.global.validation.annotation.ExistPlace; import com.otakumap.global.validation.annotation.ExistPlaceLike; +import com.otakumap.global.validation.annotation.ExistPlaceListLike; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -42,7 +45,7 @@ public ApiResponse getPlaceLikeLis @Parameters({ @Parameter(name = "placeIds", description = "저장된 장소 id List"), }) - public ApiResponse deletePlaceLike(@RequestParam(required = false) @ExistPlaceLike List placeIds) { + public ApiResponse deletePlaceLike(@RequestParam(required = false) @ExistPlaceListLike List placeIds) { placeLikeCommandService.deletePlaceLike(placeIds); return ApiResponse.onSuccess("저장된 장소가 성공적으로 삭제되었습니다"); } @@ -52,10 +55,8 @@ public ApiResponse deletePlaceLike(@RequestParam(required = false) @Exis @Parameters({ @Parameter(name = "placeId", description = "장소 Id") }) - public ApiResponse savePlaceLike(@PathVariable Long placeId, @CurrentUser User user) { - - placeLikeCommandService.savePlaceLike(user, placeId); - + public ApiResponse savePlaceLike(@ExistPlace @PathVariable Long placeId, @CurrentUser User user, @RequestBody @Valid PlaceLikeRequestDTO.SavePlaceLikeDTO request) { + placeLikeCommandService.savePlaceLike(user, placeId, request); return ApiResponse.onSuccess("장소가 성공적으로 저장되었습니다."); } @@ -64,4 +65,10 @@ public ApiResponse savePlaceLike(@PathVariable Long placeId, @CurrentUse public ApiResponse favoritePlaceLike(@PathVariable Long placeLikeId, @RequestBody @Valid PlaceLikeRequestDTO.FavoriteDTO request) { return ApiResponse.onSuccess(PlaceLikeConverter.toFavoriteResultDTO(placeLikeCommandService.favoritePlaceLike(placeLikeId, request))); } + + @Operation(summary = "저장된 장소 상세 조회", description = "지도에서 저장된 장소의 정보를 확인합니다.") + @GetMapping("/{placeLikeId}") + public ApiResponse getPlaceLike(@PathVariable @ExistPlaceLike Long placeLikeId) { + return ApiResponse.onSuccess(PlaceLikeConverter.placeLikeDetailDTO(placeLikeQueryService.getPlaceLike(placeLikeId))); + } } diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java index 9ccbad05..801074e9 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java @@ -9,4 +9,10 @@ public static class FavoriteDTO { @NotNull(message = "즐겨찾기 여부 입력은 필수입니다.") Boolean isFavorite; } + + @Getter + public static class SavePlaceLikeDTO { + @NotNull(message = "애니메이션 ID 입력은 필수입니다.") + Long animationId; + } } diff --git a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java index 07210a77..ccbc5c93 100644 --- a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java +++ b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java @@ -1,8 +1,6 @@ package com.otakumap.domain.place_like.entity; - -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -28,8 +26,8 @@ public class PlaceLike extends BaseEntity { private User user; @ManyToOne - @JoinColumn(name = "place_id", nullable = false) - private Place place; + @JoinColumn(name = "place_animation_id", nullable = false) + private PlaceAnimation placeAnimation; @Column(name = "is_favorite", nullable = false) @ColumnDefault("false") diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index 19a07d8e..c2fd3b39 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -1,23 +1,17 @@ package com.otakumap.domain.place_like.repository; -import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import java.net.ContentHandler; import java.time.LocalDateTime; -import java.util.List; public interface PlaceLikeRepository extends JpaRepository { - Page findByUserIdAndIdLessThanOrderByIdDesc(Long userId, Long lastId, Pageable pageable); - Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); - Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); - - boolean existsByUserAndPlace(User user, Place place); + boolean existsByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java index 02722557..dc182801 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java @@ -10,8 +10,6 @@ @Service public interface PlaceLikeCommandService { void deletePlaceLike(List placeIds); - - void savePlaceLike(User user, Long placeId); - + void savePlaceLike(User user, Long placeId, PlaceLikeRequestDTO.SavePlaceLikeDTO request); PlaceLike favoritePlaceLike(Long placeLikeId, PlaceLikeRequestDTO.FavoriteDTO request); } diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java index 72a62ef8..9e3e00e2 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -1,7 +1,8 @@ package com.otakumap.domain.place_like.service; -import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_like.converter.PlaceLikeConverter; import com.otakumap.domain.place_like.dto.PlaceLikeRequestDTO; import com.otakumap.domain.place_like.entity.PlaceLike; @@ -23,6 +24,7 @@ public class PlaceLikeCommandServiceImpl implements PlaceLikeCommandService { private final PlaceLikeRepository placeLikeRepository; private final EntityManager entityManager; private final PlaceRepository placeRepository; + private final PlaceAnimationRepository placeAnimationRepository; @Override public void deletePlaceLike(List placeIds) { @@ -32,16 +34,12 @@ public void deletePlaceLike(List placeIds) { } @Override - public void savePlaceLike(User user, Long placeId) { - Place place = placeRepository.findById(placeId) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - - if (placeLikeRepository.existsByUserAndPlace(user, place)) { + public void savePlaceLike(User user, Long placeId, PlaceLikeRequestDTO.SavePlaceLikeDTO request) { + PlaceAnimation placeAnimation = placeAnimationRepository.findByPlaceIdAndAnimationId(placeId, request.getAnimationId()).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); + if (placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation)) { throw new PlaceHandler(ErrorStatus.PLACE_LIKE_ALREADY_EXISTS); } - - PlaceLike placeLike = PlaceLikeConverter.toPlaceLike(user, place); - placeLikeRepository.save(placeLike); + placeLikeRepository.save(PlaceLikeConverter.toPlaceLike(user, placeAnimation)); } @Override From 467644a7cb455499ffdc532f934be9a8026c240b Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 2 Feb 2025 15:19:08 +0900 Subject: [PATCH 290/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceLikeConverter.java | 32 ++++++++++++++----- .../place_like/dto/PlaceLikeResponseDTO.java | 16 +++++++++- .../service/PlaceLikeQueryService.java | 2 ++ .../service/PlaceLikeQueryServiceImpl.java | 8 +++-- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index abde0e4a..e6a5688c 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place_like.converter; -import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; @@ -11,11 +12,11 @@ public class PlaceLikeConverter { public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(PlaceLike placeLike) { return PlaceLikeResponseDTO.PlaceLikePreViewDTO.builder() .id(placeLike.getId()) - .placeId(placeLike.getPlace().getId()) - .name(placeLike.getPlace().getName()) - .detail(placeLike.getPlace().getDetail()) - .lat(placeLike.getPlace().getLat()) - .lng(placeLike.getPlace().getLng()) + .placeId(placeLike.getPlaceAnimation().getPlace().getId()) + .name(placeLike.getPlaceAnimation().getPlace().getName()) + .detail(placeLike.getPlaceAnimation().getPlace().getDetail()) + .lat(placeLike.getPlaceAnimation().getPlace().getLat()) + .lng(placeLike.getPlaceAnimation().getPlace().getLng()) .isFavorite(placeLike.getIsFavorite()) .build(); @@ -29,10 +30,10 @@ public static PlaceLikeResponseDTO.PlaceLikePreViewListDTO placeLikePreViewListD .build(); } - public static PlaceLike toPlaceLike(User user, Place place) { + public static PlaceLike toPlaceLike(User user, PlaceAnimation placeAnimation) { return PlaceLike.builder() .user(user) - .place(place) + .placeAnimation(placeAnimation) .isFavorite(false) .build(); } @@ -43,4 +44,19 @@ public static PlaceLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(PlaceLi .isFavorite(placeLike.getIsFavorite()) .build(); } + + public static PlaceLikeResponseDTO.PlaceLikeDetailDTO placeLikeDetailDTO(PlaceLike placeLike) { + return PlaceLikeResponseDTO.PlaceLikeDetailDTO.builder() + .placeLikeId(placeLike.getId()) + .placeName(placeLike.getPlaceAnimation().getPlace().getName()) + .animationName(placeLike.getPlaceAnimation().getAnimation().getName()) + .latitude(placeLike.getPlaceAnimation().getPlace().getLat()) + .longitude(placeLike.getPlaceAnimation().getPlace().getLng()) + .isFavorite(placeLike.getIsFavorite()) + // 장소-애니메이션에 대한 해시태그 + .hashtags(placeLike.getPlaceAnimation().getPlaceAnimationHashTags() + .stream() + .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())).toList()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index c81e69c3..6fbf1b5b 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -1,6 +1,6 @@ package com.otakumap.domain.place_like.dto; -import jakarta.validation.constraints.NotNull; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -41,4 +41,18 @@ public static class FavoriteResultDTO { Long placeLikeId; Boolean isFavorite; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceLikeDetailDTO { + Long placeLikeId; + String placeName; + String animationName; + Double latitude; + Double longitude; + Boolean isFavorite; + List hashtags; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java index 5a7b39b5..685eb569 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java @@ -1,9 +1,11 @@ package com.otakumap.domain.place_like.service; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; public interface PlaceLikeQueryService { PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit); boolean isPlaceLikeExist(Long id); + PlaceLike getPlaceLike(Long placeLikeId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index a7513750..2e4cab05 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -5,9 +5,9 @@ import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -23,7 +23,6 @@ @Transactional(readOnly = true) public class PlaceLikeQueryServiceImpl implements PlaceLikeQueryService { private final PlaceLikeRepository placeLikeRepository; - private final UserRepository userRepository; @Override public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit) { @@ -66,4 +65,9 @@ private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListD public boolean isPlaceLikeExist(Long id) { return placeLikeRepository.existsById(id); } + + @Override + public PlaceLike getPlaceLike(Long placeLikeId) { + return placeLikeRepository.findById(placeLikeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + } } \ No newline at end of file From 27036cf399ef78ad89784a79eaee15119d23beba Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 2 Feb 2025 15:19:39 +0900 Subject: [PATCH 291/516] =?UTF-8?q?Chore:=20=EA=B8=B0=EC=A1=B4=20place=5Fh?= =?UTF-8?q?ashtag=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_review/converter/PlaceReviewConverter.java | 11 ++++++----- .../place_review/dto/PlaceReviewResponseDTO.java | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 548d3b7a..9ba17243 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -67,17 +67,18 @@ public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationRev List animationGroups, Float avgRating) { - List hashTagDTOs = place.getPlaceHashTagList() - .stream() - .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())) - .toList(); + // place_hashtag -> place_animation_hashtag 변경에 따라 일단 주석 처리 + //List hashTagDTOs = place.getPlaceHashTagList() + // .stream() + // .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())) + // .toList(); return PlaceReviewResponseDTO.PlaceAnimationReviewDTO.builder() .placeId(place.getId()) .placeName(place.getName()) .animationGroups(animationGroups) .totalReviews(totalReviews) - .hashTags(hashTagDTOs) + //.hashTags(hashTagDTOs) .avgRating(avgRating) .build(); } diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 196fba44..61870067 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -56,7 +56,8 @@ public static class PlaceAnimationReviewDTO { private String placeName; private Float avgRating; private long totalReviews; - private List hashTags; + // place_hashtag -> place_animation_hashtag 변경에 따라 일단 주석 처리 + //private List hashTags; private List animationGroups; } From 85c25e8bc7038dabcb9c9cd8e3ab8316a8c12f4d Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 2 Feb 2025 15:45:28 +0900 Subject: [PATCH 292/516] =?UTF-8?q?Feat:=20animationId=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AnimationRepository.java | 7 +++++ .../service/AnimationQueryService.java | 5 +++ .../service/AnimationQueryServiceImpl.java | 16 ++++++++++ .../controller/PlaceLikeController.java | 2 +- .../place_like/dto/PlaceLikeRequestDTO.java | 3 +- .../validation/annotation/ExistAnimation.java | 17 ++++++++++ .../validator/AnimationExistValidator.java | 31 +++++++++++++++++++ 7 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java diff --git a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java new file mode 100644 index 00000000..6977cd99 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.animation.repository; + +import com.otakumap.domain.animation.entity.Animation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnimationRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java new file mode 100644 index 00000000..b569c48e --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.animation.service; + +public interface AnimationQueryService { + boolean existsById(Long animationId); +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java new file mode 100644 index 00000000..b7896104 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.repository.AnimationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AnimationQueryServiceImpl implements AnimationQueryService { + private final AnimationRepository animationRepository; + + @Override + public boolean existsById(Long animationId) { + return animationRepository.existsById(animationId); + } +} diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index c0db6068..9bc6c38d 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -55,7 +55,7 @@ public ApiResponse deletePlaceLike(@RequestParam(required = false) @Exis @Parameters({ @Parameter(name = "placeId", description = "장소 Id") }) - public ApiResponse savePlaceLike(@ExistPlace @PathVariable Long placeId, @CurrentUser User user, @RequestBody @Valid PlaceLikeRequestDTO.SavePlaceLikeDTO request) { + public ApiResponse savePlaceLike(@ExistPlace @PathVariable Long placeId, @CurrentUser User user, @RequestBody @Validated PlaceLikeRequestDTO.SavePlaceLikeDTO request) { placeLikeCommandService.savePlaceLike(user, placeId, request); return ApiResponse.onSuccess("장소가 성공적으로 저장되었습니다."); } diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java index 801074e9..171fcac2 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeRequestDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.dto; +import com.otakumap.global.validation.annotation.ExistAnimation; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -12,7 +13,7 @@ public static class FavoriteDTO { @Getter public static class SavePlaceLikeDTO { - @NotNull(message = "애니메이션 ID 입력은 필수입니다.") + @ExistAnimation Long animationId; } } diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java b/src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java new file mode 100644 index 00000000..d61b520e --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.AnimationExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = AnimationExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistAnimation { + String message() default "유효하지 않은 애니메이션 ID 입니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java new file mode 100644 index 00000000..391cfecb --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java @@ -0,0 +1,31 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.animation.service.AnimationQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistAnimation; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AnimationExistValidator implements ConstraintValidator { + private final AnimationQueryService animationQueryService; + + @Override + public void initialize(ExistAnimation constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long animationId, ConstraintValidatorContext context) { + boolean isValid = animationQueryService.existsById(animationId); + + if(!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.ANIMATION_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} \ No newline at end of file From 74a1cc18a5dcf6f149e94d05a78bad785b34c961 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 2 Feb 2025 16:21:26 +0900 Subject: [PATCH 293/516] =?UTF-8?q?Refactor=20:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B4=80=EB=A0=A8=20null=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=9D=B8=EC=A6=9D=EC=9D=B4=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=20=EC=97=86=EB=8A=94=20api=20public=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/controller/EventController.java | 1 - .../converter/EventReviewConverter.java | 16 ++++++++++------ .../reviews/converter/ReviewConverter.java | 4 ++-- .../otakumap/global/config/SecurityConfig.java | 7 +++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 99e42295..12eab139 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -4,7 +4,6 @@ import com.otakumap.domain.event.service.EventCustomService; import com.otakumap.domain.event.service.EventQueryService; import com.otakumap.domain.image.dto.ImageResponseDTO; -import com.otakumap.domain.image.entity.Image; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; diff --git a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java index 33f97e6a..a793030a 100644 --- a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java +++ b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java @@ -11,12 +11,16 @@ public class EventReviewConverter { public static EventReviewResponseDTO.EventReviewPreViewDTO eventReviewPreViewDTO(EventReview eventReview) { - ImageResponseDTO.ImageDTO image = ImageResponseDTO.ImageDTO.builder() - .id(eventReview.getImages().get(0).getId()) - .uuid(eventReview.getImages().get(0).getUuid()) - .fileUrl(eventReview.getImages().get(0).getFileUrl()) - .fileName(eventReview.getImages().get(0).getFileName()) - .build(); + + ImageResponseDTO.ImageDTO image = null; + if(eventReview.getImages() != null && !eventReview.getImages().isEmpty()) { + image = ImageResponseDTO.ImageDTO.builder() + .id(eventReview.getImages().get(0).getId()) + .uuid(eventReview.getImages().get(0).getUuid()) + .fileUrl(eventReview.getImages().get(0).getFileUrl()) + .fileName(eventReview.getImages().get(0).getFileName()) + .build(); + } return EventReviewResponseDTO.EventReviewPreViewDTO.builder() .id(eventReview.getId()) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 5eaea33d..f683d777 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -15,7 +15,7 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(eventReview.getImages() != null ? + .reviewImage(eventReview.getImages() != null && !eventReview.getImages().isEmpty() ? ImageConverter.toImageDTO(eventReview.getImages().get(0)) : null) // 나중에 수정 .view(eventReview.getView()) @@ -28,7 +28,7 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(eventReview.getImages() != null ? + .reviewImage(eventReview.getImages() != null && !eventReview.getImages().isEmpty() ? ImageConverter.toImageDTO(eventReview.getImages().get(0)) : null) // 나중에 수정 .view(eventReview.getView()) diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 97cb0c57..3113aa5f 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -36,6 +37,11 @@ public class SecurityConfig { "/api/users/reset-password/**" }; + private final String[] allowGetUrl = { + "/api/events/**", + "/api/reviews/**", + }; + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http @@ -46,6 +52,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .configurationSource(CorsConfig.corsConfigurationSource())) .authorizeHttpRequests(request -> request .requestMatchers(allowUrl).permitAll() + .requestMatchers(HttpMethod.GET, allowGetUrl).permitAll() .anyRequest().authenticated()) //기본 폼 로그인 비활성화 .formLogin(AbstractHttpConfigurer::disable) From 44e13eff4763ad81f7def29b10820ee022aaeea0 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 2 Feb 2025 16:23:09 +0900 Subject: [PATCH 294/516] =?UTF-8?q?Chore=20:=20swagger=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EB=82=B4=EC=9A=A9=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/controller/EventReviewController.java | 3 ++- .../controller/EventShortReviewController.java | 3 ++- .../controller/PlaceShortReviewController.java | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java b/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java index 6000db42..516a7a9a 100644 --- a/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java +++ b/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java @@ -27,7 +27,8 @@ public class EventReviewController { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), }) @Parameters({ - @Parameter(name = "eventId", description = "이벤트의 아이디입니다.") + @Parameter(name = "eventId", description = "이벤트의 아이디입니다."), + @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0") }) public ApiResponse getEventReviewList(@PathVariable(name = "eventId") Long eventId, @RequestParam(name = "page") Integer page) { return ApiResponse.onSuccess(EventReviewConverter.eventReviewPreViewListDTO(eventReviewCommandServivce.getEventReviews(eventId, page))); diff --git a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java index b8ea08e8..f8d41cae 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java @@ -40,7 +40,8 @@ public ApiResponse createEve @Operation(summary = "이벤트 한 줄 리뷰 목록 조회", description = "특정 이벤트의 한 줄 리뷰 목록을 불러옵니다.") @GetMapping("/events/{eventId}/short-reviews") @Parameters({ - @Parameter(name = "eventId", description = "특정 이벤트의 Id") + @Parameter(name = "eventId", description = "특정 이벤트의 아이디입니다."), + @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0") }) public ApiResponse getEventShortReviewList(@PathVariable(name = "eventId") Long eventId, @RequestParam(name = "page")Integer page) { return ApiResponse.onSuccess(EventShortReviewConverter.toEventShortReviewListDTO(eventShortReviewCommandService.getEventShortReviewsByEventId(eventId, page))); diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 4b163dfe..b4135f28 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -30,12 +30,13 @@ public class PlaceShortReviewController { private final PlaceShortReviewQueryService placeShortReviewQueryService; @GetMapping("/places/{placeId}/short-review") - @Operation(summary = "특정 명소의 한 줄 리뷰 목록 조회 API", description = "특정 명소의 한 줄 리뷰 목록을 조회하는 API이며, 페이징을 포함합니다. query string으로 page 번호를 함께 보내주세요.") + @Operation(summary = "특정 명소의 한 줄 리뷰 목록 조회", description = "특정 명소의 한 줄 리뷰 목록을 불러옵니다.") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), }) @Parameters({ - @Parameter(name = "placeId", description = "명소의 아이디입니다.") + @Parameter(name = "placeId", description = "명소의 아이디입니다."), + @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0") }) public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); From c978b3bf0541b3ece35a016ac30c685d190c604f Mon Sep 17 00:00:00 2001 From: mk-star Date: Mon, 3 Feb 2025 00:31:46 +0900 Subject: [PATCH 295/516] =?UTF-8?q?Fix:=20=ED=8A=B9=EC=A0=95=20=EC=97=AC?= =?UTF-8?q?=ED=96=89=20=ED=9B=84=EA=B8=B0=20=EC=A1=B0=ED=9A=8C=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 5 +++ .../otakumap/domain/place/entity/Place.java | 3 ++ .../place_review/entity/PlaceReview.java | 5 +++ .../reviews/converter/ReviewConverter.java | 8 ++--- .../service/ReviewQueryServiceImpl.java | 36 ++----------------- .../otakumap/domain/route/entity/Route.java | 7 ++-- .../converter/RouteItemConverter.java | 6 ++-- .../route_item/dto/RouteItemResponseDTO.java | 4 +-- .../domain/route_item/entity/RouteItem.java | 17 +++------ .../domain/route_item/enums/ItemType.java | 6 ---- .../repository/RouteItemRepository.java | 12 ------- 11 files changed, 30 insertions(+), 79 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/route_item/enums/ItemType.java delete mode 100644 src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 0537d820..d915b57f 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -3,6 +3,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -55,4 +56,8 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_animation_id") private EventAnimation eventAnimation; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", referencedColumnName = "id") + private Route route; } diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 26d365a8..e0883d49 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -3,6 +3,7 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceHashTag; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.route_item.entity.RouteItem; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -48,4 +49,6 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeHashTagList = new ArrayList<>(); + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL, orphanRemoval = true) + private List routeItems = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index fe2c27df..5d533438 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -3,6 +3,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -52,4 +53,8 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", referencedColumnName = "id") + private Route route; } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 5eaea33d..2797c21a 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -63,7 +63,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr .build(); } - public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview, Route route) { + public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(placeReview.getId()) .animationName(placeReview.getPlaceAnimation().getAnimation().getName() != null ? placeReview.getPlaceAnimation().getAnimation().getName() : null) @@ -77,11 +77,11 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi .userName(placeReview.getUser().getName()) .profileImage(ImageConverter.toImageDTO(placeReview.getUser().getProfileImage())) .createdAt(placeReview.getCreatedAt()) - .route(RouteConverter.toRouteDTO(route)) + .route(RouteConverter.toRouteDTO(placeReview.getRoute())) .build(); } - public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview, Route route) { + public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(eventReview.getId()) .animationName(eventReview.getEventAnimation().getAnimation().getName() != null ? eventReview.getEventAnimation().getAnimation().getName() : null) @@ -95,7 +95,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .userName(eventReview.getUser().getName()) .profileImage(ImageConverter.toImageDTO(eventReview.getUser().getProfileImage())) .createdAt(eventReview.getCreatedAt()) - .route(RouteConverter.toRouteDTO(route)) + .route(RouteConverter.toRouteDTO(eventReview.getRoute())) .build(); } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index a107f571..a6dbd114 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -8,35 +8,25 @@ import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.enums.ItemType; -import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; -import com.otakumap.global.apiPayload.exception.handler.RouteItemHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class ReviewQueryServiceImpl implements ReviewQueryService { - private final ReviewRepositoryCustom reviewRepositoryCustom; private final EventReviewRepository eventReviewRepository; private final PlaceReviewRepository placeReviewRepository; - private final RouteItemRepository routeItemRepository; @Override public Page searchReviewsByKeyword(String keyword, int page, int size, String sort) { - return reviewRepositoryCustom.getReviewsByKeyword(keyword, page, size, sort); } @@ -47,40 +37,18 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { @Override public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewType type) { - if(type == ReviewType.EVENT) { EventReview eventReview = eventReviewRepository.findById(reviewId) .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); - Route route = getRoute(eventReview.getId(), ItemType.EVENT); - - return ReviewConverter.toEventReviewDetailDTO(eventReview, route); + return ReviewConverter.toEventReviewDetailDTO(eventReview); } else if (type == ReviewType.PLACE) { PlaceReview placeReview = placeReviewRepository.findById(reviewId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); - Route route = getRoute(placeReview.getId(), ItemType.PLACE); - - return ReviewConverter.toPlaceReviewDetailDTO(placeReview, route); + return ReviewConverter.toPlaceReviewDetailDTO(placeReview); } else { throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } - - private Route getRoute(Long reviewId, ItemType itemType) { - - List routeItems; - - if (itemType == ItemType.EVENT) { - routeItems = routeItemRepository.findByItemIdAndItemType(reviewId, ItemType.EVENT); - } else { - routeItems = routeItemRepository.findByItemIdAndItemType(reviewId, ItemType.PLACE); - } - - if (routeItems.isEmpty()) { - throw new RouteItemHandler(ErrorStatus.ROUTE_ITEM_NOT_FOUND); - } - - return routeItems.get(0).getRoute(); - } } diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java index 784477d8..bd330a68 100644 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -6,6 +6,7 @@ import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; import java.util.List; @Entity @@ -14,7 +15,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Route extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -23,9 +23,8 @@ public class Route extends BaseEntity { private String name; @OneToMany(mappedBy = "route", cascade = CascadeType.ALL) - private List routeLikes; + private List routeLikes = new ArrayList<>(); @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) - private List routeItems; - + private List routeItems = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index fcd8f09e..89ddeda8 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -4,13 +4,11 @@ import com.otakumap.domain.route_item.entity.RouteItem; public class RouteItemConverter { - public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { return RouteItemResponseDTO.RouteItemDTO.builder() .routeItemId(routeItem.getId()) - .name(routeItem.getName()) - .itemId(routeItem.getItemId()) - .itemType(routeItem.getItemType()) + .name(routeItem.getPlace().getName()) + .placeId(routeItem.getPlace().getId()) .itemOrder(routeItem.getItemOrder()) .build(); } diff --git a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java index 1c0d3c06..4ccccb8b 100644 --- a/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_item/dto/RouteItemResponseDTO.java @@ -1,6 +1,5 @@ package com.otakumap.domain.route_item.dto; -import com.otakumap.domain.route_item.enums.ItemType; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -15,8 +14,7 @@ public class RouteItemResponseDTO { public static class RouteItemDTO { Long routeItemId; String name; - Long itemId; - ItemType itemType; + Long placeId; Integer itemOrder; } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 2a89d873..df6e63d9 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -1,7 +1,7 @@ package com.otakumap.domain.route_item.entity; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_item.enums.ItemType; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -12,25 +12,18 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class RouteItem extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(length = 50, nullable = false) - private String name; - @Column(nullable = false) private Integer itemOrder; - @Enumerated(EnumType.STRING) - @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) - private ItemType itemType; - - @Column(nullable = false) - private Long itemId; // EventReview 또는 PlaceReview의 id + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "route_id") + @JoinColumn(name = "route_id", nullable = false) private Route route; } diff --git a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java deleted file mode 100644 index a6e11057..00000000 --- a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.otakumap.domain.route_item.enums; - -public enum ItemType { - PLACE, - EVENT -} diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java deleted file mode 100644 index 61497ab2..00000000 --- a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.otakumap.domain.route_item.repository; - -import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.enums.ItemType; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface RouteItemRepository extends JpaRepository { - - List findByItemIdAndItemType(Long itemId, ItemType itemType); -} From a1d9b855f73ad10397e925322c68c46eee9c41ed Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 4 Feb 2025 11:22:59 +0900 Subject: [PATCH 296/516] =?UTF-8?q?Refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20getter=20=ED=98=B8=EC=B6=9C=EC=9D=84=20=EC=A4=84?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceLikeController.java | 2 +- .../converter/PlaceLikeConverter.java | 21 ++++++++++--------- .../place_like/dto/PlaceLikeResponseDTO.java | 4 ++-- .../service/PlaceLikeQueryService.java | 3 +-- .../service/PlaceLikeQueryServiceImpl.java | 11 +++++----- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 9bc6c38d..639e5f8d 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -69,6 +69,6 @@ public ApiResponse favoritePlaceLike(@Pa @Operation(summary = "저장된 장소 상세 조회", description = "지도에서 저장된 장소의 정보를 확인합니다.") @GetMapping("/{placeLikeId}") public ApiResponse getPlaceLike(@PathVariable @ExistPlaceLike Long placeLikeId) { - return ApiResponse.onSuccess(PlaceLikeConverter.placeLikeDetailDTO(placeLikeQueryService.getPlaceLike(placeLikeId))); + return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLike(placeLikeId)); } } diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index e6a5688c..b422daa4 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -2,6 +2,7 @@ import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; @@ -9,14 +10,14 @@ import java.util.List; public class PlaceLikeConverter { - public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(PlaceLike placeLike) { + public static PlaceLikeResponseDTO.PlaceLikePreViewDTO placeLikePreViewDTO(PlaceLike placeLike, Place place) { return PlaceLikeResponseDTO.PlaceLikePreViewDTO.builder() .id(placeLike.getId()) - .placeId(placeLike.getPlaceAnimation().getPlace().getId()) - .name(placeLike.getPlaceAnimation().getPlace().getName()) - .detail(placeLike.getPlaceAnimation().getPlace().getDetail()) - .lat(placeLike.getPlaceAnimation().getPlace().getLat()) - .lng(placeLike.getPlaceAnimation().getPlace().getLng()) + .placeId(place.getId()) + .name(place.getName()) + .detail(place.getDetail()) + .lat(place.getLat()) + .lng(place.getLng()) .isFavorite(placeLike.getIsFavorite()) .build(); @@ -45,13 +46,13 @@ public static PlaceLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(PlaceLi .build(); } - public static PlaceLikeResponseDTO.PlaceLikeDetailDTO placeLikeDetailDTO(PlaceLike placeLike) { + public static PlaceLikeResponseDTO.PlaceLikeDetailDTO placeLikeDetailDTO(PlaceLike placeLike, Place place) { return PlaceLikeResponseDTO.PlaceLikeDetailDTO.builder() .placeLikeId(placeLike.getId()) - .placeName(placeLike.getPlaceAnimation().getPlace().getName()) + .placeName(place.getName()) .animationName(placeLike.getPlaceAnimation().getAnimation().getName()) - .latitude(placeLike.getPlaceAnimation().getPlace().getLat()) - .longitude(placeLike.getPlaceAnimation().getPlace().getLng()) + .lat(place.getLat()) + .lng(place.getLng()) .isFavorite(placeLike.getIsFavorite()) // 장소-애니메이션에 대한 해시태그 .hashtags(placeLike.getPlaceAnimation().getPlaceAnimationHashTags() diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 6fbf1b5b..254958da 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -50,8 +50,8 @@ public static class PlaceLikeDetailDTO { Long placeLikeId; String placeName; String animationName; - Double latitude; - Double longitude; + Double lat; + Double lng; Boolean isFavorite; List hashtags; } diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java index 685eb569..e16f727f 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java @@ -1,11 +1,10 @@ package com.otakumap.domain.place_like.service; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; -import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; public interface PlaceLikeQueryService { PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit); boolean isPlaceLikeExist(Long id); - PlaceLike getPlaceLike(Long placeLikeId); + PlaceLikeResponseDTO.PlaceLikeDetailDTO getPlaceLike(Long placeLikeId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index 2e4cab05..048f433e 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -40,10 +40,10 @@ public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, PlaceLike placeLike = placeLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); result = placeLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, placeLike.getCreatedAt(), pageable).getContent(); } - return createPlaceLikePreviewListDTO(user, result, limit); + return createPlaceLikePreviewListDTO(result, limit); } - private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(User user, List placeLikes, int limit) { + private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(List placeLikes, int limit) { boolean hasNext = placeLikes.size() > limit; Long lastId = null; @@ -54,7 +54,7 @@ private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListD List list = placeLikes .stream() - .map(PlaceLikeConverter::placeLikePreViewDTO) + .map(placeLike -> PlaceLikeConverter.placeLikePreViewDTO(placeLike, placeLike.getPlaceAnimation().getPlace())) .collect(Collectors.toList()); return PlaceLikeConverter.placeLikePreViewListDTO(list, hasNext, lastId); @@ -67,7 +67,8 @@ public boolean isPlaceLikeExist(Long id) { } @Override - public PlaceLike getPlaceLike(Long placeLikeId) { - return placeLikeRepository.findById(placeLikeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + public PlaceLikeResponseDTO.PlaceLikeDetailDTO getPlaceLike(Long placeLikeId) { + PlaceLike placeLike = placeLikeRepository.findById(placeLikeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + return PlaceLikeConverter.placeLikeDetailDTO(placeLike, placeLike.getPlaceAnimation().getPlace()); } } \ No newline at end of file From 523e29f1df56f1db9a6a59ec640b2d7adf573959 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 4 Feb 2025 11:30:25 +0900 Subject: [PATCH 297/516] =?UTF-8?q?Fix:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/place/entity/Place.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index eaaa184e..bbb46eba 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -45,9 +45,6 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) - private List placeHashTagList = new ArrayList<>(); - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL, orphanRemoval = true) private List routeItems = new ArrayList<>(); } From 507114e6f68b0874b53afac97b1e47d33706c058 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 4 Feb 2025 22:14:17 +0900 Subject: [PATCH 298/516] =?UTF-8?q?Refactor:=20=EB=AA=85=EC=86=8C=20->=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/global/validation/annotation/ExistPlace.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java b/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java index 6827b9d6..a5bb162b 100644 --- a/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistPlace.java @@ -11,8 +11,7 @@ @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface ExistPlace { - - String message() default "해당하는 명소가 존재하지 않습니다."; + String message() default "해당하는 장소가 존재하지 않습니다."; Class[] groups() default {}; Class[] payload() default {}; } From c3ed35a11bdcb8ca4d36684aed6b8545877c9701 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 4 Feb 2025 22:20:46 +0900 Subject: [PATCH 299/516] =?UTF-8?q?Refactor:=20repository=EC=97=90=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=ED=95=98=EB=8A=94=20=EA=B3=84=EC=B8=B5?= =?UTF-8?q?=EC=9D=84=20service=EB=A1=9C=20=ED=95=9C=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/service/PlaceQueryService.java | 5 +++++ .../place/service/PlaceQueryServiceImpl.java | 16 ++++++++++++++++ .../validator/PlaceExistValidator.java | 11 +++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java new file mode 100644 index 00000000..b17f2b69 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.place.service; + +public interface PlaceQueryService { + boolean isPlaceExist(Long placeId); +} diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java new file mode 100644 index 00000000..be23aeba --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.place.service; + +import com.otakumap.domain.place.repository.PlaceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PlaceQueryServiceImpl implements PlaceQueryService { + private final PlaceRepository placeRepository; + + @Override + public boolean isPlaceExist(Long placeId) { + return placeRepository.existsById(placeId); + } +} diff --git a/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java index e6b6129b..80863df8 100644 --- a/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java +++ b/src/main/java/com/otakumap/global/validation/validator/PlaceExistValidator.java @@ -1,6 +1,6 @@ package com.otakumap.global.validation.validator; -import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place.service.PlaceQueryService; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.validation.annotation.ExistPlace; import jakarta.validation.ConstraintValidator; @@ -11,8 +11,7 @@ @Component @RequiredArgsConstructor public class PlaceExistValidator implements ConstraintValidator { - - private final PlaceRepository placeRepository; + private final PlaceQueryService placeQueryService; @Override public void initialize(ExistPlace constraintAnnotation) { @@ -21,7 +20,11 @@ public void initialize(ExistPlace constraintAnnotation) { @Override public boolean isValid(Long placeId, ConstraintValidatorContext context) { - boolean isValid = placeRepository.existsById(placeId); + if (placeId == null) { + return false; + } + + boolean isValid = placeQueryService.isPlaceExist(placeId); if(!isValid) { context.disableDefaultConstraintViolation(); From 7ec2c3ba6395b06955a08bd696837abc013605a3 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 4 Feb 2025 22:23:18 +0900 Subject: [PATCH 300/516] =?UTF-8?q?Feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=A0=80=EC=9E=A5=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route/converter/RouteConverter.java | 17 +++++++++++++- .../converter/RouteItemConverter.java | 8 +++++++ .../domain/route_item/entity/RouteItem.java | 6 ++++- .../controller/RouteLikeController.java | 7 ++++++ .../route_like/dto/RouteLikeRequestDTO.java | 23 +++++++++++++++++++ .../service/RouteLikeCommandService.java | 2 +- .../service/RouteLikeCommandServiceImpl.java | 23 ++++++++++++++++++- 7 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index bf968377..fb33ab1e 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -3,11 +3,26 @@ import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.converter.RouteItemConverter; +import com.otakumap.domain.route_item.entity.RouteItem; + +import java.util.List; public class RouteConverter { + public static Route toRoute(String name, List routeItems) { + Route route = Route.builder() + .name(name) + .routeItems(routeItems) + .build(); - public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { + for (RouteItem item : routeItems) { + item.setRoute(route); + } + return route; + } + + + public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { return RouteResponseDTO.RouteDTO.builder() .routeId(route.getId()) .routeItems(route.getRouteItems().stream() diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index 89ddeda8..56db07bb 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -1,9 +1,17 @@ package com.otakumap.domain.route_item.converter; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; import com.otakumap.domain.route_item.entity.RouteItem; public class RouteItemConverter { + public static RouteItem toRouteItem(Integer itemOrder, Place place) { + return RouteItem.builder() + .itemOrder(itemOrder) + .place(place) + .build(); + } + public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { return RouteItemResponseDTO.RouteItemDTO.builder() .routeItemId(routeItem.getId()) diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index df6e63d9..87c01cc4 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -26,4 +26,8 @@ public class RouteItem extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "route_id", nullable = false) private Route route; -} + + public void setRoute(Route route) { + this.route = route; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index bd3bc8a0..2c853f6f 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -65,4 +65,11 @@ public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @Requ public ApiResponse favoriteRouteLike(@PathVariable Long routeLikeId, @RequestBody @Valid RouteLikeRequestDTO.FavoriteDTO request) { return ApiResponse.onSuccess(RouteLikeConverter.toFavoriteResultDTO(routeLikeCommandService.favoriteRouteLike(routeLikeId, request))); } + + @Operation(summary = "커스텀 루트 저장", description = "기존 루트에서 일부를 수정/삭제하여 새로운 커스텀 루트를 저장합니다.") + @PostMapping("/custom") + public ApiResponse favoriteRouteLike(@RequestBody @Valid RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, @CurrentUser User user) { + routeLikeCommandService.saveCustomRouteLike(request, user); + return ApiResponse.onSuccess("커스텀 루트가 성공적으로 저장되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java index 26003ba5..f7f2fe86 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java @@ -1,12 +1,35 @@ package com.otakumap.domain.route_like.dto; +import com.otakumap.global.validation.annotation.ExistPlace; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Getter; +import java.util.List; + public class RouteLikeRequestDTO { @Getter public static class FavoriteDTO { @NotNull(message = "즐겨찾기 여부 입력은 필수입니다.") Boolean isFavorite; } + + @Getter + public static class SaveCustomRouteLikeDTO { + @NotBlank(message = "루트 이름 입력은 필수입니다.") + String name; + @NotEmpty(message = "루트 아이템 입력은 필수입니다.") + List routeItems; + } + + @Getter + public static class RouteItemDTO { + @NotBlank(message = "루트 아이템에 해당하는 장소 이름 입력은 필수입니다.") + String name; + @ExistPlace + Long placeId; + @NotNull(message = "루트 아이템 순서 입력은 필수입니다.") + Integer itemOrder; + } } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java index 12e06d79..b9bf3c6b 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -12,6 +12,6 @@ public interface RouteLikeCommandService { void saveRouteLike(User user, Long routeId); void deleteRouteLike(List routeIds); void updateName(Long routeId, String name); - RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.FavoriteDTO request); + void saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 59412d33..704982d7 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -1,13 +1,19 @@ package com.otakumap.domain.route_like.service; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_item.converter.RouteItemConverter; +import com.otakumap.domain.route_item.entity.RouteItem; import com.otakumap.domain.route_like.converter.RouteLikeConverter; import com.otakumap.domain.route_like.dto.RouteLikeRequestDTO; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.route_like.repository.RouteLikeRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import com.otakumap.global.apiPayload.exception.handler.RouteHandler; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; @@ -15,14 +21,15 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { - private final RouteLikeRepository routeLikeRepository; private final RouteRepository routeRepository; private final EntityManager entityManager; + private final PlaceRepository placeRepository; @Override public void saveRouteLike(User user, Long routeId) { @@ -66,4 +73,18 @@ public RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.Favorit return routeLikeRepository.save(routeLike); } + @Override + public void saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user) { + List routeItems = request.getRouteItems() + .stream() + .map(routeItem -> { + Place place = placeRepository.findById(routeItem.getPlaceId()).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + return RouteItemConverter.toRouteItem(routeItem.getItemOrder(), place); + }).toList(); + + Route route = RouteConverter.toRoute(request.getName(), routeItems); + + routeRepository.save(route); + routeLikeRepository.save(RouteLikeConverter.toRouteLike(user, route)); + } } From 66fbdad5438c8a108b19c69d2fce8a7662160d83 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 6 Feb 2025 15:10:10 +0900 Subject: [PATCH 301/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route/service/RouteQueryService.java | 5 +++ .../route/service/RouteQueryServiceImpl.java | 16 +++++++++ .../validation/annotation/ExistRoute.java | 17 +++++++++ .../validator/RouteExistValidator.java | 35 +++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/route/service/RouteQueryService.java create mode 100644 src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java new file mode 100644 index 00000000..1a7eb31e --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.route.service; + +public interface RouteQueryService { + boolean isRouteExist(Long routeId); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java new file mode 100644 index 00000000..68bc2df1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.route.service; + +import com.otakumap.domain.route.repository.RouteRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RouteQueryServiceImpl implements RouteQueryService { + private final RouteRepository routeRepository; + + @Override + public boolean isRouteExist(Long routeId) { + return routeRepository.existsById(routeId); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java b/src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java new file mode 100644 index 00000000..7ed75894 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.RouteExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = RouteExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistRoute { + String message() default "유효하지 않은 루트 ID 입니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java new file mode 100644 index 00000000..43209304 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java @@ -0,0 +1,35 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.route.service.RouteQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistRoute; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RouteExistValidator implements ConstraintValidator { + private final RouteQueryService routeQueryService; + + @Override + public void initialize(ExistRoute constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long routeId, ConstraintValidatorContext context) { + if (routeId == null) { + return false; + } + + boolean isValid = routeQueryService.isRouteExist(routeId); + + if(!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.ROUTE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} From ab16733b686d37344ca94cadd3118c28f498304c Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 6 Feb 2025 15:11:13 +0900 Subject: [PATCH 302/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=88=98=EC=A0=95=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/route/entity/Route.java | 11 ++++++ .../domain/route_item/entity/RouteItem.java | 4 +++ .../controller/RouteLikeController.java | 13 ++++--- .../converter/RouteLikeConverter.java | 15 +++++++- .../route_like/dto/RouteLikeRequestDTO.java | 15 ++++++-- .../route_like/dto/RouteLikeResponseDTO.java | 20 +++++++++++ .../service/RouteLikeCommandService.java | 3 +- .../service/RouteLikeCommandServiceImpl.java | 36 +++++++++++++++++-- 8 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java index bd330a68..0f0e3ca8 100644 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -27,4 +27,15 @@ public class Route extends BaseEntity { @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) private List routeItems = new ArrayList<>(); + + public void setName(String name) { + this.name = name; + } + + public void setRouteItems(List routeItems) { + if (!this.routeItems.isEmpty()) { + this.routeItems.clear(); + } + this.routeItems.addAll(routeItems); + } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 87c01cc4..92b7a129 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -30,4 +30,8 @@ public class RouteItem extends BaseEntity { public void setRoute(Route route) { this.route = route; } + + public void setItemOrder(Integer itemOrder) { + this.itemOrder = itemOrder; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 2c853f6f..c69f337d 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -66,10 +66,15 @@ public ApiResponse favoriteRouteLike(@Pa return ApiResponse.onSuccess(RouteLikeConverter.toFavoriteResultDTO(routeLikeCommandService.favoriteRouteLike(routeLikeId, request))); } - @Operation(summary = "커스텀 루트 저장", description = "기존 루트에서 일부를 수정/삭제하여 새로운 커스텀 루트를 저장합니다.") + @Operation(summary = "커스텀 루트 저장", description = "기존 루트에서(다른 유저의 루트) 일부를 수정/삭제하여 새로운 커스텀 루트를 저장합니다.") @PostMapping("/custom") - public ApiResponse favoriteRouteLike(@RequestBody @Valid RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, @CurrentUser User user) { - routeLikeCommandService.saveCustomRouteLike(request, user); - return ApiResponse.onSuccess("커스텀 루트가 성공적으로 저장되었습니다."); + public ApiResponse saveCustomRouteLike(@RequestBody @Valid RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, @CurrentUser User user) { + return ApiResponse.onSuccess(RouteLikeConverter.toCustomRouteSaveResultDTO(routeLikeCommandService.saveCustomRouteLike(request, user))); + } + + @Operation(summary = "저장된 루트 수정", description = "저장된 루트에서 일부를 수정/삭제합니다.") + @PatchMapping("") + public ApiResponse updateRouteLike(@RequestBody @Valid RouteLikeRequestDTO.UpdateRouteLikeDTO request, @CurrentUser User user) { + return ApiResponse.onSuccess(RouteLikeConverter.toRouteUpdateResultDTO(routeLikeCommandService.updateRouteLike(request, user))); } } diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index f3f1c39e..9497c3f4 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -6,7 +6,6 @@ import com.otakumap.domain.user.entity.User; public class RouteLikeConverter { - public static RouteLike toRouteLike(User user, Route route) { return RouteLike.builder() .name(route.getName()) @@ -22,4 +21,18 @@ public static RouteLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(RouteLi .isFavorite(routeLike.getIsFavorite()) .build(); } + + public static RouteLikeResponseDTO.CustomRouteSaveResultDTO toCustomRouteSaveResultDTO(RouteLike routeLike) { + return RouteLikeResponseDTO.CustomRouteSaveResultDTO.builder() + .routeId(routeLike.getRoute().getId()) + .createdAt(routeLike.getCreatedAt()) + .build(); + } + + public static RouteLikeResponseDTO.RouteUpdateResultDTO toRouteUpdateResultDTO(RouteLike routeLike) { + return RouteLikeResponseDTO.RouteUpdateResultDTO.builder() + .routeId(routeLike.getRoute().getId()) + .updatedAt(routeLike.getUpdatedAt()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java index f7f2fe86..75d0d467 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java @@ -1,6 +1,7 @@ package com.otakumap.domain.route_like.dto; import com.otakumap.global.validation.annotation.ExistPlace; +import com.otakumap.global.validation.annotation.ExistRoute; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -25,11 +26,19 @@ public static class SaveCustomRouteLikeDTO { @Getter public static class RouteItemDTO { - @NotBlank(message = "루트 아이템에 해당하는 장소 이름 입력은 필수입니다.") - String name; @ExistPlace Long placeId; @NotNull(message = "루트 아이템 순서 입력은 필수입니다.") Integer itemOrder; } -} + + @Getter + public static class UpdateRouteLikeDTO { + @NotBlank(message = "루트 이름 입력은 필수입니다.") + String name; + @ExistRoute + Long routeId; + @NotEmpty(message = "루트 아이템 입력은 필수입니다.") + List routeItems; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java index e3a7a73e..32707877 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java @@ -5,6 +5,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + public class RouteLikeResponseDTO { @Builder @Getter @@ -14,4 +16,22 @@ public static class FavoriteResultDTO { Long routeLikeId; Boolean isFavorite; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CustomRouteSaveResultDTO { + Long routeId; + LocalDateTime createdAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RouteUpdateResultDTO { + Long routeId; + LocalDateTime updatedAt; + } } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java index b9bf3c6b..f6b9298f 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -13,5 +13,6 @@ public interface RouteLikeCommandService { void deleteRouteLike(List routeIds); void updateName(Long routeId, String name); RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.FavoriteDTO request); - void saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user); + RouteLike saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user); + RouteLike updateRouteLike(RouteLikeRequestDTO.UpdateRouteLikeDTO request, User user); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 704982d7..9614cdde 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -20,8 +20,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; @Service @RequiredArgsConstructor @@ -74,7 +76,7 @@ public RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.Favorit } @Override - public void saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user) { + public RouteLike saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user) { List routeItems = request.getRouteItems() .stream() .map(routeItem -> { @@ -85,6 +87,34 @@ public void saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO reque Route route = RouteConverter.toRoute(request.getName(), routeItems); routeRepository.save(route); - routeLikeRepository.save(RouteLikeConverter.toRouteLike(user, route)); + return routeLikeRepository.save(RouteLikeConverter.toRouteLike(user, route)); + } + + @Override + public RouteLike updateRouteLike(RouteLikeRequestDTO.UpdateRouteLikeDTO request, User user) { + Route route = routeRepository.findById(request.getRouteId()).orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); + AtomicInteger i = new AtomicInteger(); + List updatedRouteItems = new ArrayList<>(); + route.getRouteItems().forEach(routeItem -> { + if (i.get() < request.getRouteItems().size()) { + RouteLikeRequestDTO.RouteItemDTO requestItem = request.getRouteItems().get(i.get()); + + // 순서가 다르면 업데이트 + if (!Objects.equals(routeItem.getItemOrder(), requestItem.getItemOrder())) { + routeItem.setItemOrder(requestItem.getItemOrder()); + } + + Place place = placeRepository.findById(routeItem.getPlace().getId()).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + RouteItem updatedRouteItem = RouteItemConverter.toRouteItem(routeItem.getItemOrder(), place); + updatedRouteItem.setRoute(route); + updatedRouteItems.add(updatedRouteItem); + } + i.getAndIncrement(); + }); + + route.setName(request.getName()); + route.setRouteItems(updatedRouteItems); + return routeLikeRepository.save(RouteLikeConverter.toRouteLike(user, route)); } } From e5375546491c876f9bf1d9f9dd3df6294b4f793d Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 6 Feb 2025 15:13:18 +0900 Subject: [PATCH 303/516] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=ED=8E=B8=EC=A7=91=20API=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route_like/controller/RouteLikeController.java | 11 ----------- .../service/RouteLikeCommandService.java | 1 - .../service/RouteLikeCommandServiceImpl.java | 14 -------------- 3 files changed, 26 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index c69f337d..8f791376 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -49,17 +49,6 @@ public ApiResponse deleteSavedRoute(@RequestParam(required = false) @Exi return ApiResponse.onSuccess("저장된 루트가 성공적으로 삭제되었습니다."); } - @Operation(summary = "루트 제목 편집", description = "루트의 제목을 편집(수정)합니다.") - @PatchMapping("/{routeId}/name") - @Parameters({ - @Parameter(name = "routeId", description = "루트 Id") - }) - public ApiResponse updateRouteLikeName(@PathVariable Long routeId, @RequestBody @Valid UpdateNameRequestDTO request) { - routeLikeCommandService.updateName(routeId, request.name()); - - return ApiResponse.onSuccess("루트 제목이 성공적으로 수정되었습니다."); - } - @Operation(summary = "저장된 루트 즐겨찾기/즐겨찾기 취소", description = "저장된 루트를 즐겨찾기 또는 취소합니다.") @PatchMapping("/{routeLikeId}/favorites") public ApiResponse favoriteRouteLike(@PathVariable Long routeLikeId, @RequestBody @Valid RouteLikeRequestDTO.FavoriteDTO request) { diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java index f6b9298f..e8c31f40 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandService.java @@ -11,7 +11,6 @@ public interface RouteLikeCommandService { void saveRouteLike(User user, Long routeId); void deleteRouteLike(List routeIds); - void updateName(Long routeId, String name); RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.FavoriteDTO request); RouteLike saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO request, User user); RouteLike updateRouteLike(RouteLikeRequestDTO.UpdateRouteLikeDTO request, User user); diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 9614cdde..1e7a7430 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -54,20 +54,6 @@ public void deleteRouteLike(List routeIds) { entityManager.clear(); } - @Override - public void updateName(Long routeId, String name) { - - // 루트가 저장되어있는지 확인 - RouteLike routeLike = routeLikeRepository.findByRouteId(routeId) - .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); - - // 이름 변경 - routeLike.setName(name); - - // 변경된 엔티티 저장 - routeLikeRepository.save(routeLike); - } - @Override public RouteLike favoriteRouteLike(Long routeLikeId, RouteLikeRequestDTO.FavoriteDTO request) { RouteLike routeLike = routeLikeRepository.findById(routeLikeId).orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_LIKE_NOT_FOUND)); From e6df05ba4d73acdd303149b3c16ac21f17902962 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 6 Feb 2025 15:20:22 +0900 Subject: [PATCH 304/516] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=ED=8E=B8=EC=A7=91=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?setter=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route_like/entity/RouteLike.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java index e8f8de3e..04180ba8 100644 --- a/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java +++ b/src/main/java/com/otakumap/domain/route_like/entity/RouteLike.java @@ -39,10 +39,6 @@ public class RouteLike extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; - public void setName(String name) { - this.name = name; - } - public void setIsFavorite(boolean isFavorite) { this.isFavorite = isFavorite; } From 467c005d41650c96fc4fdd5a5eb4934f5ec08704 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 6 Feb 2025 22:27:26 +0900 Subject: [PATCH 305/516] =?UTF-8?q?Feat:=20@Transactional=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/service/RouteLikeCommandServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 1e7a7430..654aa99c 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -77,6 +77,7 @@ public RouteLike saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO } @Override + @Transactional public RouteLike updateRouteLike(RouteLikeRequestDTO.UpdateRouteLikeDTO request, User user) { Route route = routeRepository.findById(request.getRouteId()).orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); AtomicInteger i = new AtomicInteger(); From 00dcd31179d6d8704c4099141ca64252086e1f01 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 7 Feb 2025 04:11:28 +0900 Subject: [PATCH 306/516] =?UTF-8?q?Refactor:=20error=20status=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/global/apiPayload/code/status/ErrorStatus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 2298ce4a..34223900 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -61,7 +61,7 @@ public enum ErrorStatus implements BaseErrorCode { ANIMATION_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ANIMATION4003", "이미 존재하는 애니메이션입니다."), ANIMATION_NAME_IS_EMPTY(HttpStatus.BAD_REQUEST, "ANIMATION4004", "애니메이션 이름이 비어있습니다."), ANIMATION_NAME_LENGTH(HttpStatus.BAD_REQUEST, "ANIMATION4005", "애니메이션 이름은 2자 이상 50자 이하여야 합니다."), - ANIMATION_NAME_SPECIAL_CHARACTER(HttpStatus.BAD_REQUEST, "ANIMATION4006", "애니메이션 이름은 한글, 영문, 숫자, 공백 및 일부 특수문자(./-)만 포함할 수 있습니다."), + ANIMATION_NAME_SPECIAL_CHARACTER(HttpStatus.BAD_REQUEST, "ANIMATION4006", "애니메이션 이름은 한글, 영문, 숫자 및 일부 특수문자(./-)만 포함할 수 있습니다."), // 루트 관련 에러 ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), From 8d4410e81645027225276292984edf304b66f0b3 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 7 Feb 2025 04:26:18 +0900 Subject: [PATCH 307/516] =?UTF-8?q?Refactor:=20review=EC=99=80=20place=20e?= =?UTF-8?q?ntity=20n:m=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 4 +++ .../domain/mapping/EventReviewPlace.java | 26 +++++++++++++++++++ .../domain/mapping/PlaceReviewPlace.java | 26 +++++++++++++++++++ .../otakumap/domain/place/entity/Place.java | 8 ++++++ .../place_review/entity/PlaceReview.java | 8 +++--- 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java create mode 100644 src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 0537d820..3c8056c1 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -3,6 +3,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -44,6 +45,9 @@ public class EventReview extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") private List images = new ArrayList<>(); + @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL) + private List placeList = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; diff --git a/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java new file mode 100644 index 00000000..95ce90a2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java @@ -0,0 +1,26 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventReviewPlace extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_review_id") + private EventReview eventReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; +} diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java new file mode 100644 index 00000000..5b1079b1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java @@ -0,0 +1,26 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceReviewPlace extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_review_id") + private PlaceReview placeReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; +} diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 26d365a8..af044034 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,7 +1,9 @@ package com.otakumap.domain.place.entity; +import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceHashTag; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -48,4 +50,10 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeHashTagList = new ArrayList<>(); + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List placeReviewList = new ArrayList<>(); + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List eventReviewList = new ArrayList<>(); + } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index fe2c27df..7af9d662 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -2,6 +2,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -41,14 +42,13 @@ public class PlaceReview extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview") private List images = new ArrayList<>(); + @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL) + private List placeList = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id", nullable = false) - private Place place; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; From b7f9c7796457ceff0189edf3d61d6d0455a986f5 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 15:24:25 +0900 Subject: [PATCH 308/516] =?UTF-8?q?Chore:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/controller/PlaceLikeController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index 639e5f8d..ad9c229c 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -33,8 +33,8 @@ public class PlaceLikeController { @Operation(summary = "저장된 장소 목록 조회", description = "저장된 장소 목록을 불러옵니다.") @GetMapping("") @Parameters({ - @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 이벤트 id, 처음 가져올 때 -> 0"), - @Parameter(name = "limit", description = "한 번에 조회할 최대 이벤트 수. 기본값은 10입니다.") + @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 장소 id, 처음 가져올 때 -> 0"), + @Parameter(name = "limit", description = "한 번에 조회할 최대 장소 수. 기본값은 10입니다.") }) public ApiResponse getPlaceLikeList(@CurrentUser User user, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(user, lastId, limit)); From 4358c1de05fc99c789ad36a1e2fb30ef6a6d20ae Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 16:03:30 +0900 Subject: [PATCH 309/516] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event_like/converter/EventLikeConverter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index 10eba513..cb04c637 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -20,7 +20,6 @@ public static EventLikeResponseDTO.EventLikePreViewDTO eventLikePreViewDTO(Event .isFavorite(eventLike.getIsFavorite()) .eventType(eventLike.getEvent().getType()) .build(); - } public static EventLikeResponseDTO.EventLikePreViewListDTO eventLikePreViewListDTO(List eventLikes, boolean hasNext, Long lastId) { return EventLikeResponseDTO.EventLikePreViewListDTO.builder() From 1eac1010e170525af47e789297735b7b5fb999da Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 16:04:57 +0900 Subject: [PATCH 310/516] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event_like/converter/EventLikeConverter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java index cb04c637..1bf3bd04 100644 --- a/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java +++ b/src/main/java/com/otakumap/domain/event_like/converter/EventLikeConverter.java @@ -3,7 +3,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.event_like.entity.EventLike; -import com.otakumap.domain.image.dto.ImageResponseDTO; import com.otakumap.domain.user.entity.User; import java.util.List; From 102bfd956082ca00d687930ba1d8bb52e6b9d583 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 16:23:06 +0900 Subject: [PATCH 311/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?(+=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteLikeController.java | 14 +++++ .../converter/RouteLikeConverter.java | 20 +++++++ .../route_like/dto/RouteLikeResponseDTO.java | 25 ++++++++ .../service/RouteLikeQueryService.java | 4 ++ .../service/RouteLikeQueryServiceImpl.java | 57 +++++++++++++++++++ 5 files changed, 120 insertions(+) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 8f791376..35c8ee65 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -6,6 +6,7 @@ import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; import com.otakumap.domain.route_like.dto.UpdateNameRequestDTO; import com.otakumap.domain.route_like.service.RouteLikeCommandService; +import com.otakumap.domain.route_like.service.RouteLikeQueryService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ExistRouteLike; @@ -26,6 +27,7 @@ public class RouteLikeController { private final RouteLikeCommandService routeLikeCommandService; + private final RouteLikeQueryService routeLikeQueryService; @Operation(summary = "루트 저장", description = "루트를 저장합니다.") @PostMapping("/{routeId}") @@ -66,4 +68,16 @@ public ApiResponse saveCustomRout public ApiResponse updateRouteLike(@RequestBody @Valid RouteLikeRequestDTO.UpdateRouteLikeDTO request, @CurrentUser User user) { return ApiResponse.onSuccess(RouteLikeConverter.toRouteUpdateResultDTO(routeLikeCommandService.updateRouteLike(request, user))); } + + @Operation(summary = "저장된 루트 목록 조회(+ 즐겨찾기 목록 조회)", description = "저장된 루트 목록을 불러옵니다.") + @GetMapping("") + @Parameters({ + @Parameter(name = "isFavorite", description = "즐겨찾기 여부(필수 X) -> true: 즐겨찾기 목록 조회"), + @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 루트 id, 처음 가져올 때 -> 0"), + @Parameter(name = "limit", description = "한 번에 조회할 최대 루트 수. 기본값은 10입니다.") + }) + public ApiResponse getRouteLikeList(@CurrentUser User user, @RequestParam(required = false) Boolean isFavorite, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(routeLikeQueryService.getRouteLikeList(user, isFavorite, lastId, limit)); + } + } diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index 9497c3f4..c49bb4d5 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -1,10 +1,13 @@ package com.otakumap.domain.route_like.converter; +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.User; +import java.util.List; + public class RouteLikeConverter { public static RouteLike toRouteLike(User user, Route route) { return RouteLike.builder() @@ -35,4 +38,21 @@ public static RouteLikeResponseDTO.RouteUpdateResultDTO toRouteUpdateResultDTO(R .updatedAt(routeLike.getUpdatedAt()) .build(); } + + public static RouteLikeResponseDTO.RouteLikePreViewDTO routeLikePreViewDTO(RouteLike routeLike) { + return RouteLikeResponseDTO.RouteLikePreViewDTO.builder() + .id(routeLike.getId()) + .routeId(routeLike.getRoute().getId()) + .name(routeLike.getRoute().getName()) + .isFavorite(routeLike.getIsFavorite()) + .build(); + } + + public static RouteLikeResponseDTO.RouteLikePreViewListDTO routeLikePreViewListDTO(List routeLikes, boolean hasNext, Long lastId) { + return RouteLikeResponseDTO.RouteLikePreViewListDTO.builder() + .routeLikes(routeLikes) + .hasNext(hasNext) + .lastId(lastId) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java index 32707877..32070a54 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java @@ -1,11 +1,15 @@ package com.otakumap.domain.route_like.dto; +import com.otakumap.domain.event.entity.enums.EventType; +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; public class RouteLikeResponseDTO { @Builder @@ -34,4 +38,25 @@ public static class RouteUpdateResultDTO { Long routeId; LocalDateTime updatedAt; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RouteLikePreViewDTO { + Long id; // RoutelikeId + Long routeId; + String name; + Boolean isFavorite; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RouteLikePreViewListDTO { + List routeLikes; + boolean hasNext; + Long lastId; + } } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java index d076aee5..b400f7de 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryService.java @@ -1,5 +1,9 @@ package com.otakumap.domain.route_like.service; +import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; +import com.otakumap.domain.user.entity.User; + public interface RouteLikeQueryService { + RouteLikeResponseDTO.RouteLikePreViewListDTO getRouteLikeList(User user, Boolean isFavorite, Long lastId, int limit); boolean isRouteLikeExist(Long id); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java index 1ab647e5..6784617a 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java @@ -1,15 +1,72 @@ package com.otakumap.domain.route_like.service; +import com.otakumap.domain.event_like.converter.EventLikeConverter; +import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.event_like.entity.EventLike; +import com.otakumap.domain.route_like.converter.RouteLikeConverter; +import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; +import com.otakumap.domain.route_like.entity.QRouteLike; +import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.route_like.repository.RouteLikeRepository; +import com.otakumap.domain.user.entity.User; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class RouteLikeQueryServiceImpl implements RouteLikeQueryService { private final RouteLikeRepository routeLikeRepository; + private final JPAQueryFactory jpaQueryFactory; + + @Override + public RouteLikeResponseDTO.RouteLikePreViewListDTO getRouteLikeList(User user, Boolean isFavorite, Long lastId, int limit) { + QRouteLike qRouteLike = QRouteLike.routeLike; + BooleanBuilder predicate = new BooleanBuilder(); + + predicate.and(qRouteLike.user.eq(user)); + + // isFavorite이 true일 때만 검색 조건에 추가 + if (isFavorite != null && isFavorite) { + predicate.and(qRouteLike .isFavorite.eq(isFavorite)); + } + + if (lastId != null && lastId > 0) { + predicate.and(qRouteLike.id.lt(lastId)); + } + + List result = jpaQueryFactory + .selectFrom(qRouteLike) + .leftJoin(qRouteLike.route).fetchJoin() + .leftJoin(qRouteLike.user).fetchJoin() + .where(predicate) + .orderBy(qRouteLike.createdAt.desc()) + .limit(limit + 1) + .fetch(); + + return createRouteLikePreviewListDTO(result, limit); + } + + private RouteLikeResponseDTO.RouteLikePreViewListDTO createRouteLikePreviewListDTO(List routeLikes, int limit) { + boolean hasNext = routeLikes.size() > limit; + Long lastId = null; + if (hasNext) { + routeLikes = routeLikes.subList(0, routeLikes.size() - 1); + lastId = routeLikes.get(routeLikes.size() - 1).getId(); + } + List list = routeLikes + .stream() + .map(RouteLikeConverter::routeLikePreViewDTO) + .collect(Collectors.toList()); + + return RouteLikeConverter.routeLikePreViewListDTO(list, hasNext, lastId); + } @Override public boolean isRouteLikeExist(Long id) { From 6dee4ba388b7036c2e63de705073c287d5d81f86 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 17:09:56 +0900 Subject: [PATCH 312/516] =?UTF-8?q?Chore:=20QueryDSL=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=84=A4=EB=AA=85=20=EC=A3=BC=EC=84=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route_like/service/RouteLikeQueryServiceImpl.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java index 6784617a..b85781ce 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java @@ -30,6 +30,7 @@ public RouteLikeResponseDTO.RouteLikePreViewListDTO getRouteLikeList(User user, QRouteLike qRouteLike = QRouteLike.routeLike; BooleanBuilder predicate = new BooleanBuilder(); + //user가 좋아요(RouteLike)를 누른 데이터만 조회 predicate.and(qRouteLike.user.eq(user)); // isFavorite이 true일 때만 검색 조건에 추가 @@ -43,11 +44,11 @@ public RouteLikeResponseDTO.RouteLikePreViewListDTO getRouteLikeList(User user, List result = jpaQueryFactory .selectFrom(qRouteLike) - .leftJoin(qRouteLike.route).fetchJoin() - .leftJoin(qRouteLike.user).fetchJoin() - .where(predicate) - .orderBy(qRouteLike.createdAt.desc()) - .limit(limit + 1) + .leftJoin(qRouteLike.route).fetchJoin() // Route 테이블 조인 (N:1) + .leftJoin(qRouteLike.user).fetchJoin() // User 테이블 조인 (N:1) + .where(predicate) // 동적 필터링 적용 + .orderBy(qRouteLike.createdAt.desc()) // 최신순 정렬 + .limit(limit + 1) // limit보다 1개 더 조회 (hasNext 체크용) .fetch(); return createRouteLikePreviewListDTO(result, limit); From fad02ad969b5abd2888054c5b161a79f3a370514 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 17:14:41 +0900 Subject: [PATCH 313/516] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/service/RouteLikeQueryServiceImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java index b85781ce..03a5002e 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java @@ -1,8 +1,5 @@ package com.otakumap.domain.route_like.service; -import com.otakumap.domain.event_like.converter.EventLikeConverter; -import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; -import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.route_like.converter.RouteLikeConverter; import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; import com.otakumap.domain.route_like.entity.QRouteLike; From 903309e281467dc45381cee540b0096141056880 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 19:30:08 +0900 Subject: [PATCH 314/516] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=A0=ED=8A=B8=2050?= =?UTF-8?q?2=20gateway=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/CorsConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java index 6d65e335..2b41e124 100644 --- a/src/main/java/com/otakumap/global/config/CorsConfig.java +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -15,7 +15,11 @@ public class CorsConfig { @Bean public static CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of("http://localhost:3000")); + configuration.setAllowedOrigins(Arrays.asList( + "http://localhost:3000", + "https://otakumap.netlify.app", + "https://deploy-preview-*--otakumap.netlify.app" // 모든 프리뷰 URL 허용 + )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); From 6d4f0efdd99fcb24552f6b6f1a930fee254f1679 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 20:00:57 +0900 Subject: [PATCH 315/516] =?UTF-8?q?feat:=20PlaceLike=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20place=20=EB=A7=A4=ED=95=91=20(place?= =?UTF-8?q?=EC=97=90=EB=A7=8C=20=EC=9E=88=EC=97=88=EC=96=B4=EC=84=9C=20Que?= =?UTF-8?q?ryDSL=20=EC=83=9D=EC=84=B1=EC=9D=B4=20=EC=95=88=EB=90=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place_like/entity/PlaceLike.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java index ccbc5c93..1dd1acba 100644 --- a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java +++ b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place_like.entity; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -25,6 +26,10 @@ public class PlaceLike extends BaseEntity { @JoinColumn(name = "user_id", nullable = false) private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; + @ManyToOne @JoinColumn(name = "place_animation_id", nullable = false) private PlaceAnimation placeAnimation; From 659e07432360803ef153e64b9632cc30c50d2cfb Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Fri, 7 Feb 2025 20:12:53 +0900 Subject: [PATCH 316/516] =?UTF-8?q?feat:=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=97=AC=EB=B6=80=EA=B9=8C=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=B4=EC=84=9C=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceLikeController.java | 7 ++-- .../service/PlaceLikeQueryService.java | 2 +- .../service/PlaceLikeQueryServiceImpl.java | 38 +++++++++++-------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index ad9c229c..d2d98796 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -30,14 +30,15 @@ public class PlaceLikeController { private final PlaceLikeQueryService placeLikeQueryService; private final PlaceLikeCommandService placeLikeCommandService; - @Operation(summary = "저장된 장소 목록 조회", description = "저장된 장소 목록을 불러옵니다.") + @Operation(summary = "저장된 장소 목록 조회(+ 즐겨찾기 목록 조회)", description = "저장된 장소 목록을 불러옵니다.") @GetMapping("") @Parameters({ + @Parameter(name = "isFavorite", description = "즐겨찾기 여부(필수 X) -> true: 즐겨찾기 목록 조회"), @Parameter(name = "lastId", description = "마지막으로 조회된 저장된 장소 id, 처음 가져올 때 -> 0"), @Parameter(name = "limit", description = "한 번에 조회할 최대 장소 수. 기본값은 10입니다.") }) - public ApiResponse getPlaceLikeList(@CurrentUser User user, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { - return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(user, lastId, limit)); + public ApiResponse getPlaceLikeList(@CurrentUser User user, @RequestParam(required = false) Boolean isFavorite, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { + return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(user, isFavorite, lastId, limit)); } @Operation(summary = "저장된 장소 삭제", description = "저장된 장소를 삭제합니다.") diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java index e16f727f..a3f4a580 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java @@ -4,7 +4,7 @@ import com.otakumap.domain.user.entity.User; public interface PlaceLikeQueryService { - PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit); + PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Boolean isFavorite, Long lastId, int limit); boolean isPlaceLikeExist(Long id); PlaceLikeResponseDTO.PlaceLikeDetailDTO getPlaceLike(Long placeLikeId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index 048f433e..ce44230e 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -3,15 +3,14 @@ import com.otakumap.domain.place_like.converter.PlaceLikeConverter; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; import com.otakumap.domain.place_like.entity.PlaceLike; +import com.otakumap.domain.place_like.entity.QPlaceLike; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,23 +22,30 @@ @Transactional(readOnly = true) public class PlaceLikeQueryServiceImpl implements PlaceLikeQueryService { private final PlaceLikeRepository placeLikeRepository; + private final JPAQueryFactory jpaQueryFactory; @Override - public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Long lastId, int limit) { + public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Boolean isFavorite, Long lastId, int limit) { + QPlaceLike qPlaceLike = QPlaceLike.placeLike; + BooleanBuilder predicate = new BooleanBuilder(); - List result; - Pageable pageable = PageRequest.of(0, limit+1); + //user가 좋아요(PlaceLike)를 누른 데이터만 조회 + predicate.and(qPlaceLike.user.eq(user)); - Page placeLikePage = placeLikeRepository.findByUserIdAndIdLessThanOrderByIdDesc(user.getId(), lastId, pageable); - - if (lastId.equals(0L)) { - // lastId가 0일 경우: 처음부터 데이터를 조회 - result = placeLikeRepository.findAllByUserIsOrderByCreatedAtDesc(user, pageable).getContent(); - } else { - // lastId가 0이 아닌 경우: lastId를 기준으로 이전 데이터를 조회 - PlaceLike placeLike = placeLikeRepository.findById(lastId).orElseThrow(() -> new EventHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); - result = placeLikeRepository.findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(user, placeLike.getCreatedAt(), pageable).getContent(); + // isFavorite이 true일 때만 검색 조건에 추가 + if (isFavorite != null && isFavorite) { + predicate.and(qPlaceLike .isFavorite.eq(isFavorite)); } + + List result = jpaQueryFactory + .selectFrom(qPlaceLike) + .leftJoin(qPlaceLike.place).fetchJoin() + .leftJoin(qPlaceLike.user).fetchJoin() + .where(predicate) + .orderBy(qPlaceLike.createdAt.desc()) + .limit(limit + 1) + .fetch(); + return createPlaceLikePreviewListDTO(result, limit); } From ff54022ad6e412dd94efeb98363d65e371cf0865 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 7 Feb 2025 20:58:06 +0900 Subject: [PATCH 317/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=A1=B0=ED=9A=8C=20(=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventAnimationRepository.java | 7 + .../repository/EventLocationRepository.java | 12 ++ .../event_review/entity/EventReview.java | 2 + .../domain/mapping/PlaceReviewPlace.java | 7 + .../place/repository/PlaceRepository.java | 3 + .../controller/PlaceReviewController.java | 12 +- .../converter/PlaceReviewConverter.java | 22 +-- .../dto/PlaceReviewResponseDTO.java | 2 +- .../place_review/entity/PlaceReview.java | 2 + .../repository/PlaceReviewRepositoryImpl.java | 8 +- .../service/PlaceReviewCommandService.java | 2 +- .../PlaceReviewCommandServiceImpl.java | 30 +++-- .../reviews/controller/ReviewController.java | 4 +- .../reviews/converter/ReviewConverter.java | 60 ++++++++- .../domain/reviews/dto/ReviewRequestDTO.java | 18 ++- .../domain/reviews/dto/ReviewResponseDTO.java | 2 +- .../repository/ReviewRepositoryImpl.java | 2 +- .../reviews/service/ReviewCommandService.java | 2 +- .../service/ReviewCommandServiceImpl.java | 127 ++++++++++++++---- .../route/converter/RouteConverter.java | 13 ++ .../otakumap/domain/route/entity/Route.java | 3 +- .../converter/RouteItemConverter.java | 15 +++ .../domain/route_item/entity/RouteItem.java | 7 + 23 files changed, 287 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java diff --git a/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java b/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java new file mode 100644 index 00000000..ea70b12c --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event_animation.repository; + +import com.otakumap.domain.mapping.EventAnimation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventAnimationRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java new file mode 100644 index 00000000..82a5795a --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.event_location.repository; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event_location.entity.EventLocation; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface EventLocationRepository extends JpaRepository { + boolean existsByLatitudeAndLongitude(String lat, String lng); + Optional findByLatitudeAndLongitude(String lat, String lng); +} diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 3c8056c1..316a9c9f 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -59,4 +59,6 @@ public class EventReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_animation_id") private EventAnimation eventAnimation; + + public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java index 5b1079b1..bb48511e 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java +++ b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java @@ -23,4 +23,11 @@ public class PlaceReviewPlace extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id") private Place place; + + public void setPlaceReview(PlaceReview placeReview) { + this.placeReview = placeReview; + placeReview.getPlaceList().add(this); + } + + public void setPlace(Place place) { this.place = place; } } diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 9807a873..5addfce7 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -3,5 +3,8 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface PlaceRepository extends JpaRepository { + Optional findByNameAndLatAndLng(String name, Double lat, Double lng); } diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index f5df232f..c1c39f9a 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -21,12 +21,12 @@ public class PlaceReviewController { private final PlaceReviewCommandService placeReviewCommandService; private final PlaceReviewQueryService placeReviewQueryService; - @PostMapping("/review") - @Operation(summary = "리뷰 작성") - public ApiResponse createReview(@RequestBody @Valid PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { - PlaceReview placeReview = placeReviewCommandService.createReview(request); - return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); - } +// @PostMapping("/review") +// @Operation(summary = "리뷰 작성") +// public ApiResponse createReview(@RequestBody @Valid PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { +// PlaceReview placeReview = placeReviewCommandService.createReview(request); +// return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); +// } @GetMapping("/places/{placeId}/reviews") @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 79ea127f..99f9dfee 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -4,6 +4,7 @@ import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; @@ -12,6 +13,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Collectors; public class PlaceReviewConverter { public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { @@ -23,22 +25,22 @@ public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateRespo .build(); } - public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request, User user, Place place) { - return PlaceReview.builder() - .user(user) - .place(place) - .title(request.getTitle()) - .content(request.getContent()) - .view(0L) - .build(); - } +// public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request, User user, List placeReviewPlaces) { +// return PlaceReview.builder() +// .user(user) +// .placeList(placeReviewPlaces) +// .title(request.getTitle()) +// .content(request.getContent()) +// .view(0L) +// .build(); +// } // PlaceReview -> PlaceReviewDTO 변환 public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { return PlaceReviewResponseDTO.PlaceReviewDTO.builder() .reviewId(placeReview.getId()) - .placeId(placeReview.getPlace().getId()) + .placeIds(placeReview.getPlaceList().stream().map(prp -> prp.getPlace().getId()).collect(Collectors.toList())) .title(placeReview.getTitle()) .content(placeReview.getContent()) .view(placeReview.getView()) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 83e0f941..4c908583 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -28,7 +28,7 @@ public static class ReviewCreateResponseDTO { @AllArgsConstructor public static class PlaceReviewDTO { private Long reviewId; - private Long placeId; + private List placeIds; private String title; private String content; private Long view; diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 7af9d662..569446f7 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -52,4 +52,6 @@ public class PlaceReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; + + public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java index 6d4acb92..0beeaf01 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java @@ -14,6 +14,8 @@ import java.util.List; +import static com.otakumap.domain.mapping.QPlaceReviewPlace.placeReviewPlace; + @Repository @RequiredArgsConstructor public class PlaceReviewRepositoryImpl implements PlaceReviewRepositoryCustom { @@ -32,10 +34,12 @@ public List findAllReviewsByPlace(Long placeId, String sort) { OrderSpecifier[] orderBy = getOrderSpecifier(placeReview, sort); return queryFactory.selectFrom(placeReview) - .join(placeReview.place, place) // PlaceReview에서 Place로 조인 +// .join(placeReview.place, place) // PlaceReview에서 Place로 조인 + .join(placeReview.placeList, placeReviewPlace) // PlaceReview에서 PlaceReviewPlace로 조인 + .join(placeReviewPlace.place, place) // PlaceReviewPlace에서 Place로 조인 .join(place.placeAnimationList, placeAnimation) // Place에서 PlaceAnimation으로 조인 .join(placeAnimation.animation, animation) // PlaceAnimation에서 Animation으로 조인 - .where(placeReview.place.id.eq(placeId)) + .where(place.id.eq(placeId)) .orderBy(orderBy) .fetch(); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java index 4657b61d..e3a79e41 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -5,6 +5,6 @@ import com.otakumap.domain.user.entity.User; public interface PlaceReviewCommandService { - PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); +// PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java index 79cd5240..27b03993 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -15,6 +15,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService { @@ -22,19 +24,21 @@ public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService private final PlaceRepository placeRepository; private final UserRepository userRepository; - @Override - @Transactional - public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); - - Place place = placeRepository.findById(request.getPlaceId()) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - - PlaceReview placeReview = PlaceReviewConverter.toPlaceReview(request, user, place); - - return placeReviewRepository.save(placeReview); - } +// @Override +// @Transactional +// public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { +// User user = userRepository.findById(request.getUserId()) +// .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); +// +// List places = placeRepository.findAllById(request.getPlaceIds()); +// if (places.isEmpty()) { +// throw new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND); +// } +// +// PlaceReview placeReview = PlaceReviewConverter.toPlaceReview(request, user, places); +// +// return placeReviewRepository.save(placeReview); +// } @Override @Transactional diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index 61ea5971..f084a679 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -78,10 +78,10 @@ public ApiResponse getReviewDetail(@PathVaria @PostMapping(value = "/reviews", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다. 장소, 이벤트 후기 중 하나만 작성할 수 있으며, 최소 1개 이상의 루트 아이템이 필요합니다.") - public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) + public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) @RequestPart("request") @Valid ReviewRequestDTO.CreateDTO request, @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { - ReviewResponseDTO.createdReviewDTO createdReview = reviewCommandService.createReview(request, user, images); + ReviewResponseDTO.CreatedReviewDTO createdReview = reviewCommandService.createReview(request, user, images); return ApiResponse.onSuccess(createdReview); } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index aa90aa84..230fc05f 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,9 +1,14 @@ package com.otakumap.domain.reviews.converter; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.mapping.EventReviewPlace; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.reviews.dto.ReviewRequestDTO; @@ -62,7 +67,8 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(placeReview.getId()) - .id(placeReview.getPlace().getId()) +// .id(placeReview.getPlace().getId()) + .id(placeReview.getPlaceList().get(0).getPlace().getId()) // 해령: 임시로 수정 .title(placeReview.getTitle()) .content(placeReview.getContent()) .reviewImage(ImageConverter.toImageDTO(placeReview.getImages().get(0))) // 나중에 수정 @@ -108,31 +114,73 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .build(); } - public static ReviewResponseDTO.createdReviewDTO toCreatedReviewDTO(Long reviewId, String title) { - return ReviewResponseDTO.createdReviewDTO.builder() + public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewId, String title) { + return ReviewResponseDTO.CreatedReviewDTO.builder() .reviewId(reviewId) .title(title) .createdAt(LocalDateTime.now()) .build(); } - public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, Event event) { + public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, List eventReviewPlaces) { return EventReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) - .event(event) + .placeList(eventReviewPlaces) .build(); } - public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Place place) { + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, List placeReviewPlaces) { return PlaceReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) + .placeList(placeReviewPlaces) + .build(); + } + + public static List toPlaceReviewPlaceList(List places, PlaceReview placeReview) { + return places.stream() + .map(place -> PlaceReviewPlace.builder() + .placeReview(placeReview) + .place(place) + .build()) + .collect(Collectors.toList()); + } + + public static List toEventReviewPlaceList(List places, EventReview eventReview) { + return places.stream() + .map(place -> EventReviewPlace.builder() + .eventReview(eventReview) + .place(place) + .build()) + .collect(Collectors.toList()); + } + + public static Place toPlace(ReviewRequestDTO.RouteDTO routeDTO) { + return Place.builder() + .name(routeDTO.getName()) + .lat(routeDTO.getLat()) + .lng(routeDTO.getLng()) + .detail(routeDTO.getDetail()) + .isFavorite(false) + .build(); + } + + public static PlaceAnimation toPlaceAnimation(Place place, Animation animation) { + return PlaceAnimation.builder() .place(place) + .animation(animation) + .build(); + } + + public static EventAnimation toEventAnimation(Event event, Animation animation) { + return EventAnimation.builder() + .event(event) + .animation(animation) .build(); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java index 78522aca..8eb0c719 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -16,8 +16,8 @@ public static class CreateDTO { @NotBlank(message = "내용을 입력해주세요.") private String content; - private Long eventId; - private Long placeId; + @NotNull(message = "후기 종류를 입력해주세요. (place/event)") + private ItemType reviewType; @NotNull(message = "애니메이션 id를 입력해주세요.") private Long animeId; @@ -28,11 +28,17 @@ public static class CreateDTO { @Getter public static class RouteDTO { - @NotNull(message = "itemId를 입력해주세요.") - private Long itemId; + @NotBlank(message = "장소 이름을 입력해주세요.") + private String name; - @NotNull(message = "itemType을 입력해주세요.") - private ItemType itemType; + @NotNull + private double lat; + + @NotNull + private double lng; + + @NotBlank(message = "장소 설명을 입력해주세요.") + private String detail; @NotNull(message = "order를 입력해주세요.") private Integer order; diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index ffd938a7..634216c0 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -71,7 +71,7 @@ public static class ReviewDetailDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public static class createdReviewDTO { + public static class CreatedReviewDTO { Long reviewId; String title; LocalDateTime createdAt; diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 32ca16b4..00c0660a 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -56,7 +56,7 @@ public Page getReviewsByKeyword(Stri .fetch(); List placeReviews = queryFactory.selectFrom(placeReview) - .leftJoin(placeReview.place, QPlace.place) +// .leftJoin(placeReview.place, QPlace.place) // 해령: placeReview와 place가 N:M 관계가 되어 주석 처리 .leftJoin(QPlace.place.placeAnimationList, placeAnimation) .leftJoin(placeAnimation.animation, animation) .where(placeCondition) diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java index 93df06ed..5233d9d9 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java @@ -6,5 +6,5 @@ import org.springframework.web.multipart.MultipartFile; public interface ReviewCommandService { - ReviewResponseDTO.createdReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images); + ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index c3bc5db8..a43ad495 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -3,10 +3,16 @@ import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.repository.AnimationRepository; import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_animation.repository.EventAnimationRepository; +import com.otakumap.domain.event_location.entity.EventLocation; +import com.otakumap.domain.event_location.repository.EventLocationRepository; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.image.service.ImageCommandService; +import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.mapping.EventReviewPlace; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; @@ -15,61 +21,134 @@ import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.route.converter.RouteConverter; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_item.converter.RouteItemConverter; +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.enums.ItemType; +import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; -import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ReviewCommandServiceImpl implements ReviewCommandService { + private final PlaceReviewRepository placeReviewRepository; private final EventReviewRepository eventReviewRepository; private final AnimationRepository animationRepository; private final PlaceRepository placeRepository; - private final EventRepository eventRepository; - private final PlaceAnimationRepository placeAnimationRepository; private final RouteRepository routeRepository; + private final RouteItemRepository routeItemRepository; private final ImageCommandService imageCommandService; + private final EventLocationRepository eventLocationRepository; + private final PlaceAnimationRepository placeAnimationRepository; + private final EventAnimationRepository eventAnimationRepository; @Override @Transactional - public ReviewResponseDTO.createdReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images) { - if (request.getPlaceId() != null && request.getEventId() != null) { - throw new IllegalArgumentException("이벤트 후기와 장소 후기 중 하나만 선택해주세요."); - } else if (request.getPlaceId() == null && request.getEventId() == null) { - throw new IllegalArgumentException("이벤트 후기 또는 장소 후기 중 하나를 선택해주세요."); + public ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images) { + Animation animation = animationRepository.findById(request.getAnimeId()) + .orElseThrow(() -> new AnimationHandler(ErrorStatus.ANIMATION_NOT_FOUND)); + + Route route = saveRoute(request.getTitle()); + List routeItems = createRouteItems(request.getRouteItems(), animation, route); + routeItemRepository.saveAll(routeItems); + + return saveReview(request, user, images, route); + } + + // request의 장소 목록을 route item 객체로 변환 + private List createRouteItems(List routeDTOs, Animation animation, Route route) { + return routeDTOs.stream() + .map(routeDTO -> { + Place place = findOrSavePlace(routeDTO); + RouteItem routeItem = RouteItemConverter.toRouteItem(routeDTO, place, route); + associateAnimationWithPlaceOrEvent(place, animation, getItemType(place)); + return routeItem; + }) + .collect(Collectors.toList()); + } + + private Place findOrSavePlace(ReviewRequestDTO.RouteDTO routeDTO) { + return placeRepository.findByNameAndLatAndLng(routeDTO.getName(), routeDTO.getLat(), routeDTO.getLng()) + .orElseGet(() -> placeRepository.save(ReviewConverter.toPlace(routeDTO))); + } + + // 장소인지 이벤트인지 확인 + private ItemType getItemType(Place place) { + return eventLocationRepository.existsByLatitudeAndLongitude(place.getLat().toString(), place.getLng().toString()) ? ItemType.EVENT : ItemType.PLACE; + } + + // 장소 또는 이벤트에 애니메이션을 연결 + private void associateAnimationWithPlaceOrEvent(Place place, Animation animation, ItemType itemType) { + if (itemType == ItemType.PLACE) { + placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation)); + } else { + // Place에 해당하는 Event 찾기 + EventLocation eventLocation = eventLocationRepository.findByLatitudeAndLongitude(place.getLat().toString(), place.getLng().toString()) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.EVENT_NOT_FOUND)); + Event event = eventLocation.getEvent(); + + // Event 객체를 이용해 EventAnimation 생성 및 저장 + eventAnimationRepository.save(ReviewConverter.toEventAnimation(event, animation)); + } + } - animationRepository.findById(request.getAnimeId()) - .orElseThrow(() -> new AnimationHandler(ErrorStatus.ANIMATION_NOT_FOUND)); + private Route saveRoute(String title) { + return routeRepository.save(RouteConverter.toRoute(title, null)); + } - List reviewImages = List.of(images); + // 리뷰 저장 및 반환 + private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images, Route route) { + List places = route.getRouteItems().stream() + .map(routeItem -> placeRepository.findById(routeItem.getItemId()) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND))) + .collect(Collectors.toList()); - if (request.getPlaceId() != null) { // place review - Place place = placeRepository.findById(request.getPlaceId()) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + if (request.getReviewType() == ReviewRequestDTO.ItemType.PLACE) { + // 먼저 PlaceReview를 저장 + PlaceReview placeReview = placeReviewRepository.save(ReviewConverter.toPlaceReview(request, user, new ArrayList<>())); - PlaceReview placeReview = placeReviewRepository.save(ReviewConverter.toPlaceReview(request, user, place)); - imageCommandService.uploadReviewImages(reviewImages, placeReview.getId()); + // 저장된 PlaceReview를 기반으로 placeReviewPlaces 생성 + List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); + + // placeList 업데이트 후 다시 저장 + placeReview.setPlaceList(placeReviewPlaces); + placeReviewRepository.save(placeReview); + + imageCommandService.uploadReviewImages(List.of(images), placeReview.getId()); return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); - } - else { // event review - Event event = eventRepository.findById(request.getEventId()) - .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + } else if (request.getReviewType() == ReviewRequestDTO.ItemType.EVENT) { + // 먼저 EventReview를 저장 + EventReview eventReview = eventReviewRepository.save(ReviewConverter.toEventReview(request, user, new ArrayList<>())); - EventReview eventReview = eventReviewRepository.save(ReviewConverter.toEventReview(request, user, event)); - imageCommandService.uploadReviewImages(reviewImages, eventReview.getId()); + // 저장된 EventReview를 기반으로 eventReviewPlaces 생성 + List eventReviewPlaces = ReviewConverter.toEventReviewPlaceList(places, eventReview); + + // placeList 업데이트 후 다시 저장 + eventReview.setPlaceList(eventReviewPlaces); + eventReviewRepository.save(eventReview); + + imageCommandService.uploadReviewImages(List.of(images), eventReview.getId()); return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); + + } else { + throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index bf968377..b5a3fcdf 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -1,8 +1,14 @@ package com.otakumap.domain.route.converter; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.converter.RouteItemConverter; +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.enums.ItemType; + +import java.util.List; public class RouteConverter { @@ -14,4 +20,11 @@ public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { .map(RouteItemConverter::toRouteItemDTO).toList()) .build(); } + + public static Route toRoute(String name, List routeItems) { + return Route.builder() + .name(name) + .routeItems(routeItems) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java index 784477d8..dfc99006 100644 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -6,6 +6,7 @@ import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; import java.util.List; @Entity @@ -26,6 +27,6 @@ public class Route extends BaseEntity { private List routeLikes; @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) - private List routeItems; + private List routeItems = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index fcd8f09e..ac0cfe2d 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -1,7 +1,11 @@ package com.otakumap.domain.route_item.converter; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.enums.ItemType; public class RouteItemConverter { @@ -14,4 +18,15 @@ public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeIt .itemOrder(routeItem.getItemOrder()) .build(); } + + public static RouteItem toRouteItem(ReviewRequestDTO.RouteDTO routeDTO, Place place, Route route) { + return RouteItem.builder() + .name(routeDTO.getName()) + .itemOrder(routeDTO.getOrder()) + .itemId(place.getId()) + .itemType(ItemType.PLACE) + .route(route) + .place(place) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 2a89d873..1f0c9d43 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -1,5 +1,6 @@ package com.otakumap.domain.route_item.entity; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.enums.ItemType; import com.otakumap.global.common.BaseEntity; @@ -33,4 +34,10 @@ public class RouteItem extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "route_id") private Route route; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; + + public void setRoute(Route route) { this.route = route; } } From 1f9208cc73ac31e9acc36fce6566b76f09e8993e Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sat, 8 Feb 2025 14:05:18 +0900 Subject: [PATCH 318/516] Fix: Resolve errors and set orphanRemoval = true for specific columns in Place entity --- .../service/AnimationQueryService.java | 1 + .../service/AnimationQueryServiceImpl.java | 5 +++ .../domain/mapping/PlaceAnimationHashTag.java | 25 ++++++++++++ .../domain/place/DTO/PlaceResponseDTO.java | 29 ++++++++++++++ .../place/service/PlaceQueryService.java | 10 +++++ .../place/service/PlaceQueryServiceImpl.java | 28 ++++++++++++++ .../repository/PlaceAnimationRepository.java | 2 + .../place_review/entity/PlaceReview.java | 4 +- .../service/ReviewCommandServiceImpl.java | 5 ++- .../route/service/RouteQueryService.java | 5 +++ .../route/service/RouteQueryServiceImpl.java | 16 ++++++++ .../validation/annotation/ExistAnimation.java | 17 +++++++++ .../annotation/ExistPlaceListLike.java | 17 +++++++++ .../validation/annotation/ExistRoute.java | 17 +++++++++ .../validator/AnimationExistValidator.java | 31 +++++++++++++++ .../PlaceListLikeExistValidator.java | 38 +++++++++++++++++++ .../validator/RouteExistValidator.java | 35 +++++++++++++++++ 17 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java create mode 100644 src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/route/service/RouteQueryService.java create mode 100644 src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java create mode 100644 src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java create mode 100644 src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java index 1e5924ba..6cea0a88 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java @@ -6,4 +6,5 @@ public interface AnimationQueryService { List searchAnimation(String keyword); + boolean existsById(Long id); } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java index 984d60e6..a6b2a0c0 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -18,4 +18,9 @@ public class AnimationQueryServiceImpl implements AnimationQueryService { public List searchAnimation(String keyword) { return animationRepository.searchAnimationByKeyword(keyword); } + + @Override + public boolean existsById(Long id) { + return animationRepository.existsById(id); + } } diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java b/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java new file mode 100644 index 00000000..7c843935 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java @@ -0,0 +1,25 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceAnimationHashTag extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_animation_id") + private PlaceAnimation placeAnimation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hash_tag_id") + private HashTag hashTag; +} diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java new file mode 100644 index 00000000..edc96df2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.place.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class PlaceResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceAnimationListDTO { + List placeAnimations; + int listSize; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceAnimationDTO { + private Long placeAnimationId; + private Long animationId; + private String animationName; + } +} diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java new file mode 100644 index 00000000..3a499cc2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.place.service; + +import com.otakumap.domain.mapping.PlaceAnimation; + +import java.util.List; + +public interface PlaceQueryService { + List getPlaceAnimations(Long placeId); + boolean isPlaceExist(Long placeId); +} diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java new file mode 100644 index 00000000..eebc2fa4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.place.service; + +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import jakarta.transaction.Transactional; +import com.otakumap.domain.place.repository.PlaceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PlaceQueryServiceImpl implements PlaceQueryService { + private final PlaceRepository placeRepository; + private final PlaceAnimationRepository placeAnimationRepository; + + @Override + public boolean isPlaceExist(Long placeId) { + return placeRepository.existsById(placeId); + } + + @Override + @Transactional + public List getPlaceAnimations(Long placeId) { + return placeAnimationRepository.findByPlaceId(placeId); + } +} diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index d3f5def1..5dcc0e97 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -3,8 +3,10 @@ import com.otakumap.domain.mapping.PlaceAnimation; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface PlaceAnimationRepository extends JpaRepository { Optional findByIdAndPlaceId(Long id, Long placeId); + List findByPlaceId(Long placeId); } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 569446f7..74b330f1 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -39,10 +39,10 @@ public class PlaceReview extends BaseEntity { // @JoinColumn(name = "image_id", referencedColumnName = "id") // private Image image; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview") + @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview", orphanRemoval = true) private List images = new ArrayList<>(); - @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL, orphanRemoval = true) private List placeList = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index a43ad495..1535a036 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -109,7 +109,10 @@ private void associateAnimationWithPlaceOrEvent(Place place, Animation animation } private Route saveRoute(String title) { - return routeRepository.save(RouteConverter.toRoute(title, null)); + return routeRepository.save(Route.builder() + .name(title) + .routeItems(new ArrayList<>()) + .build()); } // 리뷰 저장 및 반환 diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java new file mode 100644 index 00000000..1a7eb31e --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.route.service; + +public interface RouteQueryService { + boolean isRouteExist(Long routeId); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java new file mode 100644 index 00000000..68bc2df1 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.route.service; + +import com.otakumap.domain.route.repository.RouteRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RouteQueryServiceImpl implements RouteQueryService { + private final RouteRepository routeRepository; + + @Override + public boolean isRouteExist(Long routeId) { + return routeRepository.existsById(routeId); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java b/src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java new file mode 100644 index 00000000..d61b520e --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistAnimation.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.AnimationExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = AnimationExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistAnimation { + String message() default "유효하지 않은 애니메이션 ID 입니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java new file mode 100644 index 00000000..6b736737 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistPlaceListLike.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.PlaceListLikeExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = PlaceListLikeExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistPlaceListLike { + String message() default "유효하지 않은 명소 ID가 포함되어 있습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java b/src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java new file mode 100644 index 00000000..7ed75894 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/annotation/ExistRoute.java @@ -0,0 +1,17 @@ +package com.otakumap.global.validation.annotation; + +import com.otakumap.global.validation.validator.RouteExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = RouteExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistRoute { + String message() default "유효하지 않은 루트 ID 입니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java new file mode 100644 index 00000000..391cfecb --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/AnimationExistValidator.java @@ -0,0 +1,31 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.animation.service.AnimationQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistAnimation; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AnimationExistValidator implements ConstraintValidator { + private final AnimationQueryService animationQueryService; + + @Override + public void initialize(ExistAnimation constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long animationId, ConstraintValidatorContext context) { + boolean isValid = animationQueryService.existsById(animationId); + + if(!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.ANIMATION_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java new file mode 100644 index 00000000..87443f81 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/PlaceListLikeExistValidator.java @@ -0,0 +1,38 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.place_like.service.PlaceLikeQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistPlaceListLike; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class PlaceListLikeExistValidator implements ConstraintValidator> { + private final PlaceLikeQueryService placeLikeQueryService; + + @Override + public void initialize(ExistPlaceListLike constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List placeIds, ConstraintValidatorContext context) { + if (placeIds == null || placeIds.isEmpty()) { + return false; + } + + boolean isValid = placeIds.stream() + .allMatch(placeId -> placeLikeQueryService.isPlaceLikeExist(placeId)); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.PLACE_LIKE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} diff --git a/src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java b/src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java new file mode 100644 index 00000000..43209304 --- /dev/null +++ b/src/main/java/com/otakumap/global/validation/validator/RouteExistValidator.java @@ -0,0 +1,35 @@ +package com.otakumap.global.validation.validator; + +import com.otakumap.domain.route.service.RouteQueryService; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.validation.annotation.ExistRoute; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RouteExistValidator implements ConstraintValidator { + private final RouteQueryService routeQueryService; + + @Override + public void initialize(ExistRoute constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long routeId, ConstraintValidatorContext context) { + if (routeId == null) { + return false; + } + + boolean isValid = routeQueryService.isRouteExist(routeId); + + if(!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.ROUTE_NOT_FOUND.toString()).addConstraintViolation(); + } + return isValid; + } +} From 8f028c0405b666942a76554e9625d0b203e6aefe Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sat, 8 Feb 2025 14:24:58 +0900 Subject: [PATCH 319/516] Feat: Add route association to EventReview and PlaceReview entities --- .../otakumap/domain/event_review/entity/EventReview.java | 5 +++++ .../otakumap/domain/place_review/entity/PlaceReview.java | 5 +++++ .../otakumap/domain/reviews/converter/ReviewConverter.java | 6 ++++-- .../domain/reviews/service/ReviewCommandServiceImpl.java | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 316a9c9f..87ef4a8f 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -4,6 +4,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -60,5 +61,9 @@ public class EventReview extends BaseEntity { @JoinColumn(name = "event_animation_id") private EventAnimation eventAnimation; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Route route; + public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 74b330f1..9210c354 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -4,6 +4,7 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -53,5 +54,9 @@ public class PlaceReview extends BaseEntity { @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Route route; + public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 230fc05f..b32d2cde 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -122,23 +122,25 @@ public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewI .build(); } - public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, List eventReviewPlaces) { + public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, List eventReviewPlaces, Route route) { return EventReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) .placeList(eventReviewPlaces) + .route(route) .build(); } - public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, List placeReviewPlaces) { + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, List placeReviewPlaces, Route route) { return PlaceReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) .placeList(placeReviewPlaces) + .route(route) .build(); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 1535a036..0f6eeaca 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -124,7 +124,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO if (request.getReviewType() == ReviewRequestDTO.ItemType.PLACE) { // 먼저 PlaceReview를 저장 - PlaceReview placeReview = placeReviewRepository.save(ReviewConverter.toPlaceReview(request, user, new ArrayList<>())); + PlaceReview placeReview = placeReviewRepository.save(ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route)); // 저장된 PlaceReview를 기반으로 placeReviewPlaces 생성 List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); @@ -138,7 +138,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO } else if (request.getReviewType() == ReviewRequestDTO.ItemType.EVENT) { // 먼저 EventReview를 저장 - EventReview eventReview = eventReviewRepository.save(ReviewConverter.toEventReview(request, user, new ArrayList<>())); + EventReview eventReview = eventReviewRepository.save(ReviewConverter.toEventReview(request, user, new ArrayList<>(), route)); // 저장된 EventReview를 기반으로 eventReviewPlaces 생성 List eventReviewPlaces = ReviewConverter.toEventReviewPlaceList(places, eventReview); From db5046202249b933c8a8f8f9f218dbb9e3c6aab8 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Sat, 8 Feb 2025 14:35:29 +0900 Subject: [PATCH 320/516] =?UTF-8?q?Fix:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place_review/entity/PlaceReview.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 9210c354..f8debcfe 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -55,7 +55,7 @@ public class PlaceReview extends BaseEntity { private PlaceAnimation placeAnimation; @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") + @JoinColumn(name = "route_id") private Route route; public void setPlaceList(List placeList) { this.placeList = placeList; } From d803c046cce3d379a85ec05560fcd3a642bae057 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sat, 8 Feb 2025 16:25:07 +0900 Subject: [PATCH 321/516] =?UTF-8?q?fix:=20get=EC=9A=94=EC=B2=AD=EC=9D=80?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=EB=8D=B0,=20post=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=94=84?= =?UTF-8?q?=EB=A1=A0=ED=8A=B8=20=EC=B8=A1=20cors=20=EC=97=90=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/CorsConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java index 2b41e124..628f44a9 100644 --- a/src/main/java/com/otakumap/global/config/CorsConfig.java +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -22,6 +22,7 @@ public static CorsConfigurationSource corsConfigurationSource() { )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); + configuration.addExposedHeader("*"); // 모든 응답 헤더 노출 configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); From b85cb82575a29b7a8d43c8bf8ad2cd82cae0a505 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sun, 9 Feb 2025 00:19:55 +0900 Subject: [PATCH 322/516] =?UTF-8?q?fix:=20setAllowedOriginPatterns()=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java index 628f44a9..b32daad4 100644 --- a/src/main/java/com/otakumap/global/config/CorsConfig.java +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -15,7 +15,7 @@ public class CorsConfig { @Bean public static CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList( + configuration.setAllowedOriginPatterns(Arrays.asList( "http://localhost:3000", "https://otakumap.netlify.app", "https://deploy-preview-*--otakumap.netlify.app" // 모든 프리뷰 URL 허용 From 89f9c0d3bf3374041fecdf14c56a4f6aaddad342 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 10 Feb 2025 01:28:46 +0900 Subject: [PATCH 323/516] =?UTF-8?q?Chore:=20=EC=95=88=20=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20import=EB=AC=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/controller/RouteLikeController.java | 2 -- .../otakumap/domain/route_like/dto/RouteLikeResponseDTO.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java index 35c8ee65..a8ea9cdf 100644 --- a/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java +++ b/src/main/java/com/otakumap/domain/route_like/controller/RouteLikeController.java @@ -4,7 +4,6 @@ import com.otakumap.domain.route_like.converter.RouteLikeConverter; import com.otakumap.domain.route_like.dto.RouteLikeRequestDTO; import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; -import com.otakumap.domain.route_like.dto.UpdateNameRequestDTO; import com.otakumap.domain.route_like.service.RouteLikeCommandService; import com.otakumap.domain.route_like.service.RouteLikeQueryService; import com.otakumap.domain.user.entity.User; @@ -79,5 +78,4 @@ public ApiResponse updateRouteLike(@R public ApiResponse getRouteLikeList(@CurrentUser User user, @RequestParam(required = false) Boolean isFavorite, @RequestParam(defaultValue = "0") Long lastId, @RequestParam(defaultValue = "10") int limit) { return ApiResponse.onSuccess(routeLikeQueryService.getRouteLikeList(user, isFavorite, lastId, limit)); } - } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java index 32070a54..45fe2970 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java @@ -1,13 +1,10 @@ package com.otakumap.domain.route_like.dto; -import com.otakumap.domain.event.entity.enums.EventType; -import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; From 7df947f6814c1bd802830055590aaa79bdcfb50a Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 10 Feb 2025 01:30:09 +0900 Subject: [PATCH 324/516] =?UTF-8?q?feat:=20=EB=A3=A8=ED=8A=B8=20=EB=82=B4?= =?UTF-8?q?=20=ED=8A=B9=EC=A0=95=20=EC=9E=A5=EC=86=8C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/DTO/PlaceResponseDTO.java | 14 +++++++ .../place/repository/PlaceRepository.java | 6 ++- .../place/service/PlaceQueryService.java | 2 + .../place/service/PlaceQueryServiceImpl.java | 37 +++++++++++++++++++ .../route/controller/RouteController.java | 37 +++++++++++++++++++ .../repository/RouteItemRepository.java | 15 ++++++++ 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/route/controller/RouteController.java create mode 100644 src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index edc96df2..44a7b62f 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -26,4 +26,18 @@ public static class PlaceAnimationDTO { private Long animationId; private String animationName; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceDetailDTO { + private Long id; + private String name; + private Boolean isSelected; + private Double latitude; + private Double longitude; + private List animeName; + private List hashtags; + } } diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 9807a873..518fbf14 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -2,6 +2,10 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; public interface PlaceRepository extends JpaRepository { -} + } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java index 3a499cc2..43687336 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java @@ -1,10 +1,12 @@ package com.otakumap.domain.place.service; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; import java.util.List; public interface PlaceQueryService { List getPlaceAnimations(Long placeId); boolean isPlaceExist(Long placeId); + PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(Long routeId, Long placeId); } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java index eebc2fa4..9f20a61f 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -1,19 +1,26 @@ package com.otakumap.domain.place.service; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import com.otakumap.domain.route_item.repository.RouteItemRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import jakarta.transaction.Transactional; import com.otakumap.domain.place.repository.PlaceRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class PlaceQueryServiceImpl implements PlaceQueryService { private final PlaceRepository placeRepository; private final PlaceAnimationRepository placeAnimationRepository; + private final RouteItemRepository routeItemRepository; @Override public boolean isPlaceExist(Long placeId) { @@ -25,4 +32,34 @@ public boolean isPlaceExist(Long placeId) { public List getPlaceAnimations(Long placeId) { return placeAnimationRepository.findByPlaceId(placeId); } + + @Override + @Transactional + public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(Long routeId, Long placeId) { + // RouteItem을 통해 Place 조회 + Place place = routeItemRepository.findPlaceByRouteIdAndPlaceId(routeId, placeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + // 애니메이션 관련 정보 조회 + List placeAnimations = placeAnimationRepository.findByPlaceId(placeId); + List animeNames = placeAnimations.stream() + .map(placeAnimation -> placeAnimation.getAnimation().getName()) + .collect(Collectors.toList()); + + // 해시태그 정보 조회 + List hashtags = placeAnimations.stream() + .flatMap(placeAnimation -> placeAnimation.getPlaceAnimationHashTags().stream()) + .map(placeAnimationHashTag -> placeAnimationHashTag.getHashTag().getName()) + .collect(Collectors.toList()); + + return PlaceResponseDTO.PlaceDetailDTO.builder() + .id(place.getId()) + .name(place.getName()) + .isSelected(Boolean.TRUE) // isSelected는 True로 가정 + .latitude(place.getLat()) + .longitude(place.getLng()) + .animeName(animeNames) + .hashtags(hashtags) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route/controller/RouteController.java b/src/main/java/com/otakumap/domain/route/controller/RouteController.java new file mode 100644 index 00000000..e638a4ae --- /dev/null +++ b/src/main/java/com/otakumap/domain/route/controller/RouteController.java @@ -0,0 +1,37 @@ +package com.otakumap.domain.route.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.service.PlaceQueryService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/route") +@RequiredArgsConstructor +@Validated +public class RouteController { + private final PlaceQueryService placeQueryService; + + @Operation(summary = "루트 내 특정 장소 상세 정보 조회", description = "주어진 routeId와 placeId를 기반으로 특정 장소의 상세 정보를 불러옵니다.") + @GetMapping("{routeId}/{placeId}") + @Parameters({ + @Parameter(name = "routeId", description = "루트 ID"), + @Parameter(name = "placeId", description = "루트 내 특정 장소 ID") + }) + public ApiResponse getPlaceDetail( + @PathVariable Long routeId, + @PathVariable Long placeId) { + PlaceResponseDTO.PlaceDetailDTO placeDetail = placeQueryService.getPlaceDetail(routeId, placeId); + return ApiResponse.onSuccess(placeDetail); + } +} diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java new file mode 100644 index 00000000..4d0e1cd3 --- /dev/null +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -0,0 +1,15 @@ +package com.otakumap.domain.route_item.repository; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.route_item.entity.RouteItem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface RouteItemRepository extends JpaRepository { + + @Query("SELECT ri.place FROM RouteItem ri WHERE ri.route.id = :routeId AND ri.place.id = :placeId") + Optional findPlaceByRouteIdAndPlaceId(@Param("routeId") Long routeId, @Param("placeId") Long placeId); +} From 559a433e6d3ce936732635fd40f30e621ca20784 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 10 Feb 2025 01:47:50 +0900 Subject: [PATCH 325/516] =?UTF-8?q?feat:=20=ED=95=B4=EB=8B=B9=20=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=EC=9D=98=20=EC=A2=8B=EC=95=84=EC=9A=94&=EC=A6=90?= =?UTF-8?q?=EA=B2=A8=EC=B0=BE=EA=B8=B0=20=EC=97=AC=EB=B6=80=EB=8F=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/DTO/PlaceResponseDTO.java | 3 ++- .../domain/place/repository/PlaceRepository.java | 4 ---- .../domain/place/service/PlaceQueryService.java | 3 ++- .../domain/place/service/PlaceQueryServiceImpl.java | 12 ++++++++++-- .../place_like/repository/PlaceLikeRepository.java | 9 +++------ .../domain/route/controller/RouteController.java | 3 ++- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index 44a7b62f..b45915cb 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -34,9 +34,10 @@ public static class PlaceAnimationDTO { public static class PlaceDetailDTO { private Long id; private String name; - private Boolean isSelected; private Double latitude; private Double longitude; + private Boolean isFavorite; + private Boolean isLiked; private List animeName; private List hashtags; } diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 518fbf14..5b787841 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -2,10 +2,6 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.Optional; public interface PlaceRepository extends JpaRepository { } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java index 43687336..8bd24d2b 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java @@ -2,11 +2,12 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.user.entity.User; import java.util.List; public interface PlaceQueryService { List getPlaceAnimations(Long placeId); boolean isPlaceExist(Long placeId); - PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(Long routeId, Long placeId); + PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId); } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java index 9f20a61f..94b48337 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -4,7 +4,9 @@ import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.route_item.repository.RouteItemRepository; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import jakarta.transaction.Transactional; @@ -21,6 +23,7 @@ public class PlaceQueryServiceImpl implements PlaceQueryService { private final PlaceRepository placeRepository; private final PlaceAnimationRepository placeAnimationRepository; private final RouteItemRepository routeItemRepository; + private final PlaceLikeRepository placeLikeRepository; @Override public boolean isPlaceExist(Long placeId) { @@ -35,7 +38,7 @@ public List getPlaceAnimations(Long placeId) { @Override @Transactional - public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(Long routeId, Long placeId) { + public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId) { // RouteItem을 통해 Place 조회 Place place = routeItemRepository.findPlaceByRouteIdAndPlaceId(routeId, placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); @@ -52,12 +55,17 @@ public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(Long routeId, Long placeId .map(placeAnimationHashTag -> placeAnimationHashTag.getHashTag().getName()) .collect(Collectors.toList()); + + // 특정 사용자가 해당 Place를 좋아요 했는지 여부 확인 + boolean isLiked = placeLikeRepository.findByPlaceIdAndUserId(placeId, user.getId()).isPresent(); + return PlaceResponseDTO.PlaceDetailDTO.builder() .id(place.getId()) .name(place.getName()) - .isSelected(Boolean.TRUE) // isSelected는 True로 가정 .latitude(place.getLat()) .longitude(place.getLng()) + .isFavorite(place.getIsFavorite()) + .isLiked(isLiked) .animeName(animeNames) .hashtags(hashtags) .build(); diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index c2fd3b39..d5b42168 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -3,15 +3,12 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import java.time.LocalDateTime; +import java.util.Optional; public interface PlaceLikeRepository extends JpaRepository { - Page findByUserIdAndIdLessThanOrderByIdDesc(Long userId, Long lastId, Pageable pageable); - Page findAllByUserIsOrderByCreatedAtDesc(User user, Pageable pageable); - Page findAllByUserIsAndCreatedAtLessThanOrderByCreatedAtDesc(User user, LocalDateTime createdAt, Pageable pageable); boolean existsByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); + // 특정 Place와 연결된 PlaceLike가 존재하는지 확인 + Optional findByPlaceIdAndUserId(Long placeId, Long userId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/controller/RouteController.java b/src/main/java/com/otakumap/domain/route/controller/RouteController.java index e638a4ae..1986eba0 100644 --- a/src/main/java/com/otakumap/domain/route/controller/RouteController.java +++ b/src/main/java/com/otakumap/domain/route/controller/RouteController.java @@ -29,9 +29,10 @@ public class RouteController { @Parameter(name = "placeId", description = "루트 내 특정 장소 ID") }) public ApiResponse getPlaceDetail( + @CurrentUser User user, @PathVariable Long routeId, @PathVariable Long placeId) { - PlaceResponseDTO.PlaceDetailDTO placeDetail = placeQueryService.getPlaceDetail(routeId, placeId); + PlaceResponseDTO.PlaceDetailDTO placeDetail = placeQueryService.getPlaceDetail(user, routeId, placeId); return ApiResponse.onSuccess(placeDetail); } } From 0a3004dd43340abe88fa37a32894ac20bb6946cb Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Mon, 10 Feb 2025 02:08:33 +0900 Subject: [PATCH 326/516] =?UTF-8?q?Feature:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EB=B3=84=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 18 +++- .../event/converter/EventConverter.java | 15 +++ .../domain/event/dto/EventResponseDTO.java | 14 ++- .../repository/EventRepositoryCustom.java | 3 + .../event/repository/EventRepositoryImpl.java | 98 ++++++++++++++++++- .../event/service/EventCustomService.java | 2 + .../event/service/EventCustomServiceImpl.java | 18 ++++ .../apiPayload/code/status/ErrorStatus.java | 6 ++ 8 files changed, 168 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 12eab139..5a4e7918 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -1,5 +1,6 @@ package com.otakumap.domain.event.controller; +import com.otakumap.domain.event.converter.EventConverter; import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.service.EventCustomService; import com.otakumap.domain.event.service.EventQueryService; @@ -8,11 +9,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -43,4 +42,15 @@ public ApiResponse getEventDetail(@PathVariable public ApiResponse getBanner() { return ApiResponse.onSuccess(eventCustomService.getEventBanner()); } + + @Operation(summary = "카테고리별로 이벤트 검색", description = "카테고리별로 이벤트를 검색합니다.") + @GetMapping("/events/category") + public ApiResponse searchEventByCategory( + @Parameter(description = "애니메이션 장르 (ALL, ROMANCE, ACTION, FANTASY, THRILLER, SPORTS)", example = "ALL") @RequestParam(required = false) String genre, + @Parameter(description = "이벤트 상태 (IN_PROCESS, NOT_STARTED)") @RequestParam(required = false) String status, + @Parameter(description = "이벤트 종류 (ALL, POPUP_STORE, EXHIBITION, COLLABORATION_CAFE)") @RequestParam(required = false) String type, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam Integer page, + @RequestParam Integer size) { + return ApiResponse.onSuccess(EventConverter.toEventSearchResultDTO(eventCustomService.searchEventByCategory(genre, status, type, page, size))); + } } diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 2cdf6ac5..0ca4b9b6 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -4,6 +4,11 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_location.converter.EventLocationConverter; import com.otakumap.domain.image.converter.ImageConverter; +import org.springframework.data.domain.Page; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; public class EventConverter { @@ -32,4 +37,14 @@ public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { .eventLocation(EventLocationConverter.toEventLocationDTO(event.getEventLocation())) .build(); } + + public static EventResponseDTO.EventSearchResultDTO toEventSearchResultDTO(Page events) { + return EventResponseDTO.EventSearchResultDTO.builder() + .events(events.getContent()) + .pageNumber(events.getNumber()) + .totalPages(events.getTotalPages()) + .totalElements(events.getNumberOfElements()) + .isLast(events.isLast()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index c0da1f19..a6d6ea13 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -6,8 +6,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; - import java.time.LocalDate; +import java.util.List; public class EventResponseDTO { @@ -40,4 +40,16 @@ public static class EventDetailDTO { ImageResponseDTO.ImageDTO goodsImage; EventLocationResponseDTO.EventLocationDTO eventLocation; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventSearchResultDTO { + List events; + Integer pageNumber; + Integer totalPages; + Integer totalElements; + Boolean isLast; + } } diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java index a5ab245d..f8c2ff59 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java @@ -2,10 +2,13 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; +import org.springframework.data.domain.Page; import java.util.List; public interface EventRepositoryCustom { List getPopularEvents(); ImageResponseDTO.ImageDTO getEventBanner(); + Page getEventByGenre(String genre, Integer page, Integer size); + Page getEventByStatusAndType(String status, String type, Integer page, Integer size); } diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index 0c102c3c..a4398462 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -4,16 +4,23 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.entity.QEvent; +import com.otakumap.domain.event.entity.enums.EventType; +import com.otakumap.domain.event.entity.enums.Genre; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.dto.ImageResponseDTO; -import com.otakumap.domain.image.entity.Image; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Repository; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -59,4 +66,93 @@ public ImageResponseDTO.ImageDTO getEventBanner() { return ImageConverter.toImageDTO((targetEvent) .getThumbnailImage()); } + + @Override + public Page getEventByGenre(String genre, Integer page, Integer size) { + QEvent event = QEvent.event; + BooleanBuilder searchCondition = new BooleanBuilder(); + + switch (genre) { + case "ALL": + break; + case "ROMANCE": + searchCondition.and(event.genre.eq(Genre.ROMANCE)); + break; + case "ACTION": + searchCondition.and(event.genre.eq(Genre.ACTION)); + break; + case "FANTASY": + searchCondition.and(event.genre.eq(Genre.FANTASY)); + break; + case "THRILLER": + searchCondition.and(event.genre.eq(Genre.THRILLER)); + break; + case "SPORTS": + searchCondition.and(event.genre.eq(Genre.SPORTS)); + break; + default: + throw new EventHandler(ErrorStatus.EVENT_GENRE_NOT_FOUND); + } + + return paginateSearchEventResults(page, size, event, searchCondition); + } + + @Override + public Page getEventByStatusAndType(String status, String type, Integer page, Integer size) { + QEvent event = QEvent.event; + BooleanBuilder searchCondition = new BooleanBuilder(); + + + if(type != null && !type.isBlank()) { + switch (type) { + case "ALL": + break; + case "POPUP_STORE": + searchCondition.and(event.type.eq(EventType.POPUP_STORE)); + break; + case "EXHIBITION": + searchCondition.and(event.type.eq(EventType.EXHIBITION)); + break; + case "COLLABORATION_CAFE": + searchCondition.and(event.type.eq(EventType.COLLABORATION_CAFE)); + break; + default: + throw new EventHandler(ErrorStatus.EVENT_TYPE_NOT_FOUND); + } + } + + if(status != null && !status.isBlank()) { + switch (status) { + case "IN_PROCESS": + searchCondition.and(event.startDate.loe(LocalDate.now())) + .and(event.endDate.goe(LocalDate.now())); + break; + case "NOT_STARTED": + searchCondition.and(event.startDate.goe(LocalDate.now()) + .and(event.endDate.goe(LocalDate.now()))); + break; + default: + throw new EventHandler(ErrorStatus.EVENT_STATUS_NOT_FOUND); + } + } + + return paginateSearchEventResults(page, size, event, searchCondition); + } + + private Page paginateSearchEventResults(Integer page, Integer size, QEvent event, BooleanBuilder searchCondition) { + List events = queryFactory.selectFrom(event) + .where(searchCondition) + .fetch(); + events.sort(Comparator.comparing(Event::getEndDate) + .thenComparing(Event::getTitle)); + + + int start = page * size; + int end = Math.min(start + size, events.size()); + if(start > end) { + return new PageImpl<>(new ArrayList<>(), PageRequest.of(page, size), events.size()); + } + + return new PageImpl<>(events.stream().map(EventConverter::toEventDTO).collect(Collectors.toList()), PageRequest.of(page, size), events.size()); + } } diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java index b41eaf72..38134c93 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java @@ -2,10 +2,12 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; +import org.springframework.data.domain.Page; import java.util.List; public interface EventCustomService { List getPopularEvents(); ImageResponseDTO.ImageDTO getEventBanner(); + Page searchEventByCategory(String genre, String status, String type, Integer page, Integer size); } diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java index 342dace6..6f146b46 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java @@ -3,7 +3,10 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.repository.EventRepositoryCustom; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.EventHandler; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -23,4 +26,19 @@ public List getPopularEvents() { public ImageResponseDTO.ImageDTO getEventBanner() { return eventRepository.getEventBanner(); } + + @Override + public Page searchEventByCategory(String genre, String status, String type, Integer page, Integer size) { + + if (genre == null || genre.isBlank()) { + if ((status == null || status.isBlank()) && (type == null || type.isBlank())) { + // status, type, genre 모두 null인 경우 에러 발생 + throw new EventHandler(ErrorStatus.EVENT_CONDITION_NOT_FOUND); + } + // genre는 null, status나 type이 존재할 경우 + return eventRepository.getEventByStatusAndType(status, type, page, size); + } + // genre가 존재할 경우 처리 + return eventRepository.getEventByGenre(genre, page, size); + } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 06c8791c..896c0f7d 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -52,6 +52,12 @@ public enum ErrorStatus implements BaseErrorCode { // 이벤트 후기 관련 에러 EVENT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4003", "존재하지 않는 이벤트 후기입니다."), + // 이벤트 카테고리별 검색 관련 에러 + EVENT_CONDITION_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4004", "이벤트 검색 조건이 존재하지 않습니다."), + EVENT_GENRE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4005", "존재하지 않는 애니메이션 장르입니다."), + EVENT_TYPE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4006", "존재하지 않는 이벤트 종류입니다."), + EVENT_STATUS_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4007", "존재하지 않는 이벤트 상태입니다."), + // 후기 검색 관련 에러 REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), From 1f924fece0a3a1cfed83afc81204f7693a8ce260 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 10 Feb 2025 10:43:24 +0900 Subject: [PATCH 327/516] =?UTF-8?q?feat:=20placeId=EA=B0=80=20=EC=97=86?= =?UTF-8?q?=EC=9C=BC=EB=A9=B4=20placeHandler,=20routeId=EA=B0=80=20?= =?UTF-8?q?=EC=97=86=EC=9C=BC=EB=A9=B4=20RouteHandler=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/service/PlaceQueryServiceImpl.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java index 94b48337..2783adeb 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -5,10 +5,12 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.RouteHandler; import jakarta.transaction.Transactional; import com.otakumap.domain.place.repository.PlaceRepository; import lombok.RequiredArgsConstructor; @@ -24,6 +26,7 @@ public class PlaceQueryServiceImpl implements PlaceQueryService { private final PlaceAnimationRepository placeAnimationRepository; private final RouteItemRepository routeItemRepository; private final PlaceLikeRepository placeLikeRepository; + private final RouteRepository routeRepository; @Override public boolean isPlaceExist(Long placeId) { @@ -39,6 +42,12 @@ public List getPlaceAnimations(Long placeId) { @Override @Transactional public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId) { + // routeId가 존재하는지 먼저 확인 + boolean routeExists = routeRepository.existsById(routeId); + if (!routeExists) { + throw new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND); + } + // RouteItem을 통해 Place 조회 Place place = routeItemRepository.findPlaceByRouteIdAndPlaceId(routeId, placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); From 578c1d573b939f2782a34fe36d14ca6b3f1eae13 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 10 Feb 2025 11:21:39 +0900 Subject: [PATCH 328/516] =?UTF-8?q?feat:=20=EB=A3=A8=ED=8A=B8=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/DTO/PlaceResponseDTO.java | 11 +++++++ .../route/controller/RouteController.java | 15 ++++++++++ .../domain/route/dto/RouteResponseDTO.java | 10 +++++++ .../route/service/RouteQueryService.java | 4 +++ .../route/service/RouteQueryServiceImpl.java | 30 +++++++++++++++++++ .../repository/RouteItemRepository.java | 4 +++ 6 files changed, 74 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index b45915cb..f879daaf 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -41,4 +41,15 @@ public static class PlaceDetailDTO { private List animeName; private List hashtags; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PlaceDTO { + private Long id; + private String name; + private Double latitude; + private Double longitude; + } } diff --git a/src/main/java/com/otakumap/domain/route/controller/RouteController.java b/src/main/java/com/otakumap/domain/route/controller/RouteController.java index 1986eba0..8a18547d 100644 --- a/src/main/java/com/otakumap/domain/route/controller/RouteController.java +++ b/src/main/java/com/otakumap/domain/route/controller/RouteController.java @@ -3,6 +3,8 @@ import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.service.PlaceQueryService; +import com.otakumap.domain.route.dto.RouteResponseDTO; +import com.otakumap.domain.route.service.RouteQueryService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -21,6 +23,7 @@ @Validated public class RouteController { private final PlaceQueryService placeQueryService; + private final RouteQueryService routeQueryService; @Operation(summary = "루트 내 특정 장소 상세 정보 조회", description = "주어진 routeId와 placeId를 기반으로 특정 장소의 상세 정보를 불러옵니다.") @GetMapping("{routeId}/{placeId}") @@ -35,4 +38,16 @@ public ApiResponse getPlaceDetail( PlaceResponseDTO.PlaceDetailDTO placeDetail = placeQueryService.getPlaceDetail(user, routeId, placeId); return ApiResponse.onSuccess(placeDetail); } + + @Operation(summary = "루트 상세 정보 조회", description = "주어진 routeId를 기반으로 루트의 상세 정보를 불러옵니다.") + @GetMapping("{routeId}") + @Parameters({ + @Parameter(name = "routeId", description = "루트 ID") + }) + public ApiResponse getRouteDetail( + @CurrentUser User user, + @PathVariable Long routeId) { + RouteResponseDTO.RouteDetailDTO routeDetail = routeQueryService.getRouteDetail(user, routeId); + return ApiResponse.onSuccess(routeDetail); + } } diff --git a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java index c86489eb..2653b80b 100644 --- a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.route.dto; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,4 +19,13 @@ public static class RouteDTO { Long routeId; List routeItems; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RouteDetailDTO { + private Long routeId; + private List places; + } } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java index 1a7eb31e..aceea7b8 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryService.java @@ -1,5 +1,9 @@ package com.otakumap.domain.route.service; +import com.otakumap.domain.route.dto.RouteResponseDTO; +import com.otakumap.domain.user.entity.User; + public interface RouteQueryService { boolean isRouteExist(Long routeId); + RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 68bc2df1..8b3308c6 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -1,16 +1,46 @@ package com.otakumap.domain.route.service; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.route.dto.RouteResponseDTO; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_item.repository.RouteItemRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.RouteHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class RouteQueryServiceImpl implements RouteQueryService { private final RouteRepository routeRepository; + private final RouteItemRepository routeItemRepository; @Override public boolean isRouteExist(Long routeId) { return routeRepository.existsById(routeId); } + + @Override + public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { + Route route = routeRepository.findById(routeId) + .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); + + // routeId로 Place 목록 조회 + List places = routeItemRepository.findPlacesByRouteId(routeId) + .stream() + .map(place -> new PlaceResponseDTO.PlaceDTO( + place.getId(), + place.getName(), + place.getLat(), + place.getLng() + )) + .collect(Collectors.toList()); + + return new RouteResponseDTO.RouteDetailDTO(route.getId(), places); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java index 4d0e1cd3..63af4314 100644 --- a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -6,10 +6,14 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface RouteItemRepository extends JpaRepository { @Query("SELECT ri.place FROM RouteItem ri WHERE ri.route.id = :routeId AND ri.place.id = :placeId") Optional findPlaceByRouteIdAndPlaceId(@Param("routeId") Long routeId, @Param("placeId") Long placeId); + + @Query("SELECT ri.place FROM RouteItem ri WHERE ri.route.id = :routeId") + List findPlacesByRouteId(@Param("routeId") Long routeId); } From 2a8d80c488017fe4e60b652718171d8020dc2adf Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 10 Feb 2025 11:52:04 +0900 Subject: [PATCH 329/516] =?UTF-8?q?refactor:=20converter=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place/DTO/PlaceResponseDTO.java | 2 +- .../place/converter/PlaceConverter.java | 37 ++++++++++++++++++- .../place/service/PlaceQueryServiceImpl.java | 22 ++--------- .../route/service/RouteQueryServiceImpl.java | 12 +----- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index f879daaf..e1648287 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -38,7 +38,7 @@ public static class PlaceDetailDTO { private Double longitude; private Boolean isFavorite; private Boolean isLiked; - private List animeName; + private PlaceResponseDTO.PlaceAnimationListDTO animationListDTO; private List hashtags; } diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index c672ace6..4a7e8810 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -1,8 +1,8 @@ package com.otakumap.domain.place.converter; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.entity.Place; import org.springframework.stereotype.Component; import java.util.List; @@ -26,4 +26,39 @@ public static PlaceResponseDTO.PlaceAnimationListDTO toPlaceAnimationListDTO(Lis .listSize(placeAnimations.size()) .build(); } + + public static PlaceResponseDTO.PlaceDetailDTO toPlaceDetailDTO(Place place, PlaceResponseDTO.PlaceAnimationListDTO animationListDTO, List hashtags, boolean isLiked) { + return PlaceResponseDTO.PlaceDetailDTO.builder() + .id(place.getId()) + .name(place.getName()) + .latitude(place.getLat()) + .longitude(place.getLng()) + .isFavorite(place.getIsFavorite()) + .isLiked(isLiked) + .animationListDTO(animationListDTO) + .hashtags(hashtags) + .build(); + } + + public static List toPlaceHashtagsDTO(List placeAnimations) { + return placeAnimations.stream() + .flatMap(placeAnimation -> placeAnimation.getPlaceAnimationHashTags().stream()) + .map(placeAnimationHashTag -> placeAnimationHashTag.getHashTag().getName()) + .collect(Collectors.toList()); + } + + public static PlaceResponseDTO.PlaceDTO toPlaceDTO(Place place) { + return new PlaceResponseDTO.PlaceDTO( + place.getId(), + place.getName(), + place.getLat(), + place.getLng() + ); + } + + public static List toPlaceDTOList(List places) { + return places.stream() + .map(PlaceConverter::toPlaceDTO) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java index 2783adeb..079606e1 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -2,6 +2,7 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.converter.PlaceConverter; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; @@ -17,7 +18,6 @@ import org.springframework.stereotype.Service; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -54,29 +54,15 @@ public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, L // 애니메이션 관련 정보 조회 List placeAnimations = placeAnimationRepository.findByPlaceId(placeId); - List animeNames = placeAnimations.stream() - .map(placeAnimation -> placeAnimation.getAnimation().getName()) - .collect(Collectors.toList()); + PlaceResponseDTO.PlaceAnimationListDTO animationListDTO = PlaceConverter.toPlaceAnimationListDTO(placeAnimations); // 해시태그 정보 조회 - List hashtags = placeAnimations.stream() - .flatMap(placeAnimation -> placeAnimation.getPlaceAnimationHashTags().stream()) - .map(placeAnimationHashTag -> placeAnimationHashTag.getHashTag().getName()) - .collect(Collectors.toList()); + List hashtags = PlaceConverter.toPlaceHashtagsDTO(placeAnimations); // 특정 사용자가 해당 Place를 좋아요 했는지 여부 확인 boolean isLiked = placeLikeRepository.findByPlaceIdAndUserId(placeId, user.getId()).isPresent(); - return PlaceResponseDTO.PlaceDetailDTO.builder() - .id(place.getId()) - .name(place.getName()) - .latitude(place.getLat()) - .longitude(place.getLng()) - .isFavorite(place.getIsFavorite()) - .isLiked(isLiked) - .animeName(animeNames) - .hashtags(hashtags) - .build(); + return PlaceConverter.toPlaceDetailDTO(place, animationListDTO, hashtags, isLiked); } } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 8b3308c6..7adf52bc 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -1,6 +1,7 @@ package com.otakumap.domain.route.service; import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.converter.PlaceConverter; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; @@ -12,7 +13,6 @@ import org.springframework.stereotype.Service; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -31,15 +31,7 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); // routeId로 Place 목록 조회 - List places = routeItemRepository.findPlacesByRouteId(routeId) - .stream() - .map(place -> new PlaceResponseDTO.PlaceDTO( - place.getId(), - place.getName(), - place.getLat(), - place.getLng() - )) - .collect(Collectors.toList()); + List places = PlaceConverter.toPlaceDTOList(routeItemRepository.findPlacesByRouteId(routeId)); return new RouteResponseDTO.RouteDetailDTO(route.getId(), places); } From 230d2078ef9ffe29094a6aa198513272e6258f36 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 12:29:05 +0900 Subject: [PATCH 330/516] =?UTF-8?q?Refactor:=20place=20entity=EC=9D=98=20n?= =?UTF-8?q?ame=20=EA=B8=B8=EC=9D=B4=20=EC=A6=9D=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/place/entity/Place.java | 2 +- .../com/otakumap/domain/reviews/converter/ReviewConverter.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index af044034..3253471f 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -25,7 +25,7 @@ public class Place extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 50) private String name; @Column(nullable = false) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index b32d2cde..751a361f 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -130,6 +130,7 @@ public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User .user(user) .placeList(eventReviewPlaces) .route(route) + .rating(0F) .build(); } From 74f9a781444786d0dda3c90023b4d59dad674939 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 12:56:45 +0900 Subject: [PATCH 331/516] =?UTF-8?q?Fix:=20image=EC=97=90=20review=5Fid=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20event=5Freview=EC=97=90=20rout?= =?UTF-8?q?e=5Fid=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 4 ++-- .../otakumap/domain/image/entity/Image.java | 4 ++++ .../image/service/ImageCommandService.java | 3 ++- .../service/ImageCommandServiceImpl.java | 24 ++++++++++++++++--- .../service/ReviewCommandServiceImpl.java | 7 ++---- .../apiPayload/code/status/ErrorStatus.java | 1 + 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 87ef4a8f..ddc78116 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -27,7 +27,7 @@ public class EventReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(length = 20, nullable = false) + @Column(length = 50, nullable = false) private String title; @Column(columnDefinition = "text not null") @@ -62,7 +62,7 @@ public class EventReview extends BaseEntity { private EventAnimation eventAnimation; @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") + @JoinColumn(name = "route_id") private Route route; public void setPlaceList(List placeList) { this.placeList = placeList; } diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java index 91f5c9bf..c733d55a 100644 --- a/src/main/java/com/otakumap/domain/image/entity/Image.java +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -33,4 +33,8 @@ public class Image extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_review_id") private EventReview eventReview; + + public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } + + public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java index 9df0bb58..55342b6b 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -1,13 +1,14 @@ package com.otakumap.domain.image.service; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.route_item.enums.ItemType; import org.springframework.web.multipart.MultipartFile; import java.util.List; public interface ImageCommandService { Image uploadProfileImage(MultipartFile file, Long userId); - List uploadReviewImages(List files, Long reviewId); + List uploadReviewImages(List files, Long reviewId, ItemType reviewType); Image uploadImage(MultipartFile file, String folder); } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java index f335b4ea..e344eb19 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -1,10 +1,16 @@ package com.otakumap.domain.image.service; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.repository.ImageRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.route_item.enums.ItemType; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.ImageHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.util.AmazonS3Util; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -19,6 +25,8 @@ public class ImageCommandServiceImpl implements ImageCommandService { private final ImageRepository imageRepository; private final AmazonS3Util amazonS3Util; + private final EventReviewRepository eventReviewRepository; + private final PlaceReviewRepository placeReviewRepository; @Override @Transactional @@ -31,15 +39,25 @@ public Image uploadProfileImage(MultipartFile file, Long userId) { @Override @Transactional - public List uploadReviewImages(List files, Long reviewId) { + public List uploadReviewImages(List files, Long reviewId, ItemType reviewType) { return files.stream() .map(file -> { String keyName = amazonS3Util.generateReviewKeyName(); String fileUrl = amazonS3Util.uploadFile(keyName, file); + Image image = ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); - return ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); + if (reviewType == ItemType.EVENT) { + EventReview eventReview = eventReviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + image.setEventReview(eventReview); + } else if (reviewType == ItemType.PLACE) { + PlaceReview placeReview = placeReviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + image.setPlaceReview(placeReview); + } + + return imageRepository.save(image); }) - .map(imageRepository::save) .collect(Collectors.toList()); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 0f6eeaca..863f0919 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -9,9 +9,7 @@ import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.image.service.ImageCommandService; -import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; -import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; @@ -21,7 +19,6 @@ import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_item.converter.RouteItemConverter; @@ -133,7 +130,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO placeReview.setPlaceList(placeReviewPlaces); placeReviewRepository.save(placeReview); - imageCommandService.uploadReviewImages(List.of(images), placeReview.getId()); + imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ItemType.PLACE); return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); } else if (request.getReviewType() == ReviewRequestDTO.ItemType.EVENT) { @@ -147,7 +144,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO eventReview.setPlaceList(eventReviewPlaces); eventReviewRepository.save(eventReview); - imageCommandService.uploadReviewImages(List.of(images), eventReview.getId()); + imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ItemType.EVENT); return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); } else { diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 34223900..0daedd63 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -85,6 +85,7 @@ public enum ErrorStatus implements BaseErrorCode { // 여행 후기 관련 에러 INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), + REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4003", "존재하지 않는 후기입니다."), // 이미지 관련 에러 INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), From 574db15281b175273dd55f0bd623c49ac3c6e923 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 13:19:25 +0900 Subject: [PATCH 332/516] Refactor: Merge branch dev into feature/#13 --- .../domain/animation/service/AnimationQueryService.java | 1 - .../domain/animation/service/AnimationQueryServiceImpl.java | 5 ----- src/main/java/com/otakumap/domain/place/entity/Place.java | 5 +---- .../com/otakumap/domain/route_item/entity/RouteItem.java | 2 +- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java index f0040c52..6070b6f8 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java @@ -6,6 +6,5 @@ public interface AnimationQueryService { List searchAnimation(String keyword); - boolean existsById(Long id); boolean existsById(Long animationId); } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java index cf94edf8..4bf5d88e 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -23,9 +23,4 @@ public boolean existsById(Long animationId) { public List searchAnimation(String keyword) { return animationRepository.searchAnimationByKeyword(keyword); } - - @Override - public boolean existsById(Long id) { - return animationRepository.existsById(id); - } } diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index f934b32a..585b506c 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -2,9 +2,9 @@ import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.PlaceHashTag; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.route_item.entity.RouteItem; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -47,9 +47,6 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) - private List placeHashTagList = new ArrayList<>(); - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeReviewList = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 7124c73c..9d3545e8 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -29,7 +29,7 @@ public class RouteItem extends BaseEntity { private ItemType itemType; @Column(nullable = false) - private Long itemId; // EventReview 또는 PlaceReview의 id + private Long itemId; // Event 또는 Place의 id @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "route_id", nullable = false) From becc1fe8a418006a653d887d1753775542de9326 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 13:35:19 +0900 Subject: [PATCH 333/516] =?UTF-8?q?Refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceReviewController.java | 8 -------- .../converter/PlaceReviewConverter.java | 11 ----------- .../dto/PlaceReviewRequestDTO.java | 19 ------------------- .../dto/PlaceReviewResponseDTO.java | 11 ----------- .../service/PlaceReviewCommandService.java | 2 -- .../PlaceReviewCommandServiceImpl.java | 17 ----------------- 6 files changed, 68 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index c1c39f9a..f695c69b 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -18,16 +18,8 @@ @RequiredArgsConstructor @RequestMapping("/api") public class PlaceReviewController { - private final PlaceReviewCommandService placeReviewCommandService; private final PlaceReviewQueryService placeReviewQueryService; -// @PostMapping("/review") -// @Operation(summary = "리뷰 작성") -// public ApiResponse createReview(@RequestBody @Valid PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { -// PlaceReview placeReview = placeReviewCommandService.createReview(request); -// return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); -// } - @GetMapping("/places/{placeId}/reviews") @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") @Parameters({ diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index e6e7fb4e..2460ca98 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -6,7 +6,6 @@ import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.entity.User; @@ -25,16 +24,6 @@ public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateRespo .build(); } -// public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request, User user, List placeReviewPlaces) { -// return PlaceReview.builder() -// .user(user) -// .placeList(placeReviewPlaces) -// .title(request.getTitle()) -// .content(request.getContent()) -// .view(0L) -// .build(); -// } - // PlaceReview -> PlaceReviewDTO 변환 public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java deleted file mode 100644 index 304c8abf..00000000 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.otakumap.domain.place_review.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import lombok.Getter; - -public class PlaceReviewRequestDTO { - @Getter - public static class ReviewCreateRequestDTO { - private Long userId; // 토큰 사용 전 임시로 사용 - private Long placeId; // 임시 - @NotBlank - @Size(max = 20) - private String title; - @NotBlank - private String content; - // TODO: 이미지, 장소 활용 - } -} diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 4457fd86..f938f0e8 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -11,17 +11,6 @@ import java.util.List; public class PlaceReviewResponseDTO { - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class ReviewCreateResponseDTO { - private Long reviewId; - private String title; - private String content; - LocalDateTime createdAt; - } - @Builder @Getter @NoArgsConstructor diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java index e3a79e41..b24306a6 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -1,10 +1,8 @@ package com.otakumap.domain.place_review.service; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.entity.User; public interface PlaceReviewCommandService { -// PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java index 27b03993..81e49a61 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -3,7 +3,6 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.user.entity.User; @@ -24,22 +23,6 @@ public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService private final PlaceRepository placeRepository; private final UserRepository userRepository; -// @Override -// @Transactional -// public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { -// User user = userRepository.findById(request.getUserId()) -// .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); -// -// List places = placeRepository.findAllById(request.getPlaceIds()); -// if (places.isEmpty()) { -// throw new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND); -// } -// -// PlaceReview placeReview = PlaceReviewConverter.toPlaceReview(request, user, places); -// -// return placeReviewRepository.save(placeReview); -// } - @Override @Transactional public void deleteAllByUserId(Long userId) { From 86e2f313d0805f2e5dfa76f8a8fe9c05325a76f3 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 13:36:34 +0900 Subject: [PATCH 334/516] =?UTF-8?q?Refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_review/controller/PlaceReviewController.java | 1 - .../place_review/converter/PlaceReviewConverter.java | 9 --------- 2 files changed, 10 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index f695c69b..76bb6a92 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -1,7 +1,6 @@ package com.otakumap.domain.place_review.controller; import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.service.PlaceReviewCommandService; diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 2460ca98..49cb1b08 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -15,15 +15,6 @@ import java.util.stream.Collectors; public class PlaceReviewConverter { - public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { - return PlaceReviewResponseDTO.ReviewCreateResponseDTO.builder() - .reviewId(placeReview.getId()) - .title(placeReview.getTitle()) - .content(placeReview.getContent()) - .createdAt(LocalDateTime.now()) - .build(); - } - // PlaceReview -> PlaceReviewDTO 변환 public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { From 703349ad4005401113336021822835c2570bde0c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 14:29:13 +0900 Subject: [PATCH 335/516] =?UTF-8?q?Fix:=20mapping=20table=EC=97=90=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=80=EC=9E=A5=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventReviewPlaceRepository.java | 7 +++++++ .../domain/mapping/EventReviewPlace.java | 2 ++ .../domain/mapping/PlaceReviewPlace.java | 5 +---- .../place_review/entity/PlaceReview.java | 2 +- .../PlaceReviewPlaceRepository.java | 7 +++++++ .../service/ReviewCommandServiceImpl.java | 21 ++++++++++++++----- .../repository/RouteItemRepository.java | 2 ++ .../apiPayload/code/status/ErrorStatus.java | 1 + 8 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java create mode 100644 src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java diff --git a/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java new file mode 100644 index 00000000..ab386f0c --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event_review_place.repository; + +import com.otakumap.domain.mapping.EventReviewPlace; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventReviewPlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java index 95ce90a2..f4f7805a 100644 --- a/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java +++ b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java @@ -23,4 +23,6 @@ public class EventReviewPlace extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id") private Place place; + + public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } } diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java index bb48511e..070d2238 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java +++ b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java @@ -24,10 +24,7 @@ public class PlaceReviewPlace extends BaseEntity { @JoinColumn(name = "place_id") private Place place; - public void setPlaceReview(PlaceReview placeReview) { - this.placeReview = placeReview; - placeReview.getPlaceList().add(this); - } + public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } public void setPlace(Place place) { this.place = place; } } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 34617558..3c3e0d69 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -27,7 +27,7 @@ public class PlaceReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 50) private String title; @Column(nullable = false, length = 3000) diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java new file mode 100644 index 00000000..ec4844e2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place_review_place.repository; + +import com.otakumap.domain.mapping.PlaceReviewPlace; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceReviewPlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 863f0919..657f4e15 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -8,6 +8,7 @@ import com.otakumap.domain.event_location.repository.EventLocationRepository; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.domain.event_review_place.repository.EventReviewPlaceRepository; import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceReviewPlace; @@ -16,6 +17,7 @@ import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; @@ -53,6 +55,8 @@ public class ReviewCommandServiceImpl implements ReviewCommandService { private final EventLocationRepository eventLocationRepository; private final PlaceAnimationRepository placeAnimationRepository; private final EventAnimationRepository eventAnimationRepository; + private final PlaceReviewPlaceRepository placeReviewPlaceRepository; + private final EventReviewPlaceRepository eventReviewPlaceRepository; @Override @Transactional @@ -114,35 +118,41 @@ private Route saveRoute(String title) { // 리뷰 저장 및 반환 private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images, Route route) { - List places = route.getRouteItems().stream() + List routeItems = routeItemRepository.findByRouteId(route.getId()); + + List places = routeItems.stream() .map(routeItem -> placeRepository.findById(routeItem.getItemId()) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND))) .collect(Collectors.toList()); if (request.getReviewType() == ReviewRequestDTO.ItemType.PLACE) { // 먼저 PlaceReview를 저장 - PlaceReview placeReview = placeReviewRepository.save(ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route)); + PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); + placeReview = placeReviewRepository.save(placeReview); // 저장된 PlaceReview를 기반으로 placeReviewPlaces 생성 List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); + placeReviewPlaceRepository.saveAll(placeReviewPlaces); // placeList 업데이트 후 다시 저장 placeReview.setPlaceList(placeReviewPlaces); - placeReviewRepository.save(placeReview); + placeReview = placeReviewRepository.save(placeReview); imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ItemType.PLACE); return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); } else if (request.getReviewType() == ReviewRequestDTO.ItemType.EVENT) { // 먼저 EventReview를 저장 - EventReview eventReview = eventReviewRepository.save(ReviewConverter.toEventReview(request, user, new ArrayList<>(), route)); + EventReview eventReview = ReviewConverter.toEventReview(request, user, new ArrayList<>(), route); + eventReview = eventReviewRepository.save(eventReview); // 저장된 EventReview를 기반으로 eventReviewPlaces 생성 List eventReviewPlaces = ReviewConverter.toEventReviewPlaceList(places, eventReview); + eventReviewPlaceRepository.saveAll(eventReviewPlaces); // placeList 업데이트 후 다시 저장 eventReview.setPlaceList(eventReviewPlaces); - eventReviewRepository.save(eventReview); + eventReview = eventReviewRepository.save(eventReview); imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ItemType.EVENT); return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); @@ -151,4 +161,5 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } + } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java index 63af4314..93d1ee52 100644 --- a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -16,4 +16,6 @@ public interface RouteItemRepository extends JpaRepository { @Query("SELECT ri.place FROM RouteItem ri WHERE ri.route.id = :routeId") List findPlacesByRouteId(@Param("routeId") Long routeId); + + List findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 0daedd63..4f0539c2 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -86,6 +86,7 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4003", "존재하지 않는 후기입니다."), + DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "REVIEW5001", "데이터베이스 오류가 발생했습니다."), // 이미지 관련 에러 INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), From fb7419a3772cba9d66476f69295734ae5a51d053 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 14:59:25 +0900 Subject: [PATCH 336/516] =?UTF-8?q?Fix:=20RouteItem=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EB=90=98=EB=8F=8C=EB=A6=AC=EA=B8=B0=20=EB=B0=8F=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=EC=95=8A=EB=8A=94=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 1 - .../controller/PlaceReviewController.java | 4 ---- .../converter/PlaceReviewConverter.java | 2 +- .../dto/PlaceReviewResponseDTO.java | 2 +- .../service/PlaceReviewCommandService.java | 3 --- .../PlaceReviewCommandServiceImpl.java | 13 ------------ .../route/converter/RouteConverter.java | 7 ------- .../converter/RouteItemConverter.java | 21 ------------------- .../domain/route_item/entity/RouteItem.java | 18 +++------------- .../domain/route_item/enums/ItemType.java | 6 ------ .../apiPayload/code/status/ErrorStatus.java | 1 - 11 files changed, 5 insertions(+), 73 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/route_item/enums/ItemType.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index b67c58db..c93da32d 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -5,7 +5,6 @@ import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index 76bb6a92..8d0a3680 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -1,15 +1,11 @@ package com.otakumap.domain.place_review.controller; -import com.otakumap.domain.place_review.converter.PlaceReviewConverter; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.place_review.service.PlaceReviewCommandService; import com.otakumap.domain.place_review.service.PlaceReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 49cb1b08..517f408e 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -20,7 +20,7 @@ public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview return PlaceReviewResponseDTO.PlaceReviewDTO.builder() .reviewId(placeReview.getId()) - .placeIds(placeReview.getPlaceList().stream().map(prp -> prp.getPlace().getId()).collect(Collectors.toList())) + .placeIds(placeReview.getPlaceList().stream().map(prp -> prp.getPlace().getId()).collect(Collectors.toList())) // 해령: placeId -> placeIds로 변경 .title(placeReview.getTitle()) .content(placeReview.getContent()) .view(placeReview.getView()) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index f938f0e8..709e0d84 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -17,7 +17,7 @@ public class PlaceReviewResponseDTO { @AllArgsConstructor public static class PlaceReviewDTO { private Long reviewId; - private List placeIds; + private List placeIds; // 해령: ids로 수정 private String title; private String content; private Long view; diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java index b24306a6..a0fc05f3 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -1,8 +1,5 @@ package com.otakumap.domain.place_review.service; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.user.entity.User; - public interface PlaceReviewCommandService { void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java index 81e49a61..eaf67f53 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -1,27 +1,14 @@ package com.otakumap.domain.place_review.service; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService { private final PlaceReviewRepository placeReviewRepository; - private final PlaceRepository placeRepository; - private final UserRepository userRepository; @Override @Transactional diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index af4e9007..693ac1ab 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -24,13 +24,6 @@ public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { .build(); } -// public static Route toRoute(String name, List routeItems) { -// return Route.builder() -// .name(name) -// .routeItems(routeItems) -// .build(); -// } - public static Route toRoute(String name, List routeItems) { Route route = Route.builder() .name(name) diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index 568b77f2..384fe961 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -16,16 +16,6 @@ public static RouteItem toRouteItem(Integer itemOrder, Place place) { .build(); } -// public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { -// return RouteItemResponseDTO.RouteItemDTO.builder() -// .routeItemId(routeItem.getId()) -// .name(routeItem.getName()) -// .itemId(routeItem.getItemId()) -// .itemType(routeItem.getItemType()) -// .itemOrder(routeItem.getItemOrder()) -// .build(); -// } - public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { return RouteItemResponseDTO.RouteItemDTO.builder() .routeItemId(routeItem.getId()) @@ -34,15 +24,4 @@ public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeIt .itemOrder(routeItem.getItemOrder()) .build(); } - - public static RouteItem toRouteItem(ReviewRequestDTO.RouteDTO routeDTO, Place place, Route route) { - return RouteItem.builder() - .name(routeDTO.getName()) - .itemOrder(routeDTO.getOrder()) - .itemId(place.getId()) - .itemType(ItemType.PLACE) - .route(route) - .place(place) - .build(); - } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 9d3545e8..92b7a129 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -2,7 +2,6 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_item.enums.ItemType; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -13,32 +12,21 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class RouteItem extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(length = 50, nullable = false) - private String name; - @Column(nullable = false) private Integer itemOrder; - @Enumerated(EnumType.STRING) - @Column(columnDefinition = "VARCHAR(10) DEFAULT 'PLACE'", nullable = false) - private ItemType itemType; - - @Column(nullable = false) - private Long itemId; // Event 또는 Place의 id + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "route_id", nullable = false) private Route route; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") - private Place place; - public void setRoute(Route route) { this.route = route; } diff --git a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java b/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java deleted file mode 100644 index 53dab523..00000000 --- a/src/main/java/com/otakumap/domain/route_item/enums/ItemType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.otakumap.domain.route_item.enums; - -public enum ItemType { - PLACE, - EVENT -} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 4f0539c2..0daedd63 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -86,7 +86,6 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4003", "존재하지 않는 후기입니다."), - DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "REVIEW5001", "데이터베이스 오류가 발생했습니다."), // 이미지 관련 에러 INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), From ba40fc0d391ce97577889bcb3a58d569a0b81044 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 15:30:12 +0900 Subject: [PATCH 337/516] =?UTF-8?q?Fix:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20Ro?= =?UTF-8?q?uteItem=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/service/ImageCommandService.java | 4 ++-- .../service/ImageCommandServiceImpl.java | 8 +++---- .../domain/reviews/dto/ReviewRequestDTO.java | 7 ++---- .../service/ReviewCommandServiceImpl.java | 23 ++++++++++--------- .../route/converter/RouteConverter.java | 6 ----- .../converter/RouteItemConverter.java | 4 ---- .../domain/route_item/entity/RouteItem.java | 4 ++++ 7 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java index 55342b6b..7ab1825f 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -1,14 +1,14 @@ package com.otakumap.domain.image.service; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.route_item.enums.ItemType; +import com.otakumap.domain.reviews.enums.ReviewType; import org.springframework.web.multipart.MultipartFile; import java.util.List; public interface ImageCommandService { Image uploadProfileImage(MultipartFile file, Long userId); - List uploadReviewImages(List files, Long reviewId, ItemType reviewType); + List uploadReviewImages(List files, Long reviewId, ReviewType reviewType); Image uploadImage(MultipartFile file, String folder); } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java index e344eb19..71d6a63e 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -7,7 +7,7 @@ import com.otakumap.domain.image.repository.ImageRepository; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.route_item.enums.ItemType; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.ImageHandler; import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; @@ -39,18 +39,18 @@ public Image uploadProfileImage(MultipartFile file, Long userId) { @Override @Transactional - public List uploadReviewImages(List files, Long reviewId, ItemType reviewType) { + public List uploadReviewImages(List files, Long reviewId, ReviewType reviewType) { return files.stream() .map(file -> { String keyName = amazonS3Util.generateReviewKeyName(); String fileUrl = amazonS3Util.uploadFile(keyName, file); Image image = ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); - if (reviewType == ItemType.EVENT) { + if (reviewType == ReviewType.EVENT) { EventReview eventReview = eventReviewRepository.findById(reviewId) .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); image.setEventReview(eventReview); - } else if (reviewType == ItemType.PLACE) { + } else if (reviewType == ReviewType.PLACE) { PlaceReview placeReview = placeReviewRepository.findById(reviewId) .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); image.setPlaceReview(placeReview); diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java index 8eb0c719..bb725425 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.reviews.dto; +import com.otakumap.domain.reviews.enums.ReviewType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -17,7 +18,7 @@ public static class CreateDTO { private String content; @NotNull(message = "후기 종류를 입력해주세요. (place/event)") - private ItemType reviewType; + private ReviewType reviewType; @NotNull(message = "애니메이션 id를 입력해주세요.") private Long animeId; @@ -43,8 +44,4 @@ public static class RouteDTO { @NotNull(message = "order를 입력해주세요.") private Integer order; } - - public enum ItemType { - PLACE, EVENT - } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 657f4e15..78a6cbef 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -21,11 +21,11 @@ import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_item.converter.RouteItemConverter; import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.enums.ItemType; import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; @@ -76,7 +76,8 @@ private List createRouteItems(List routeDT return routeDTOs.stream() .map(routeDTO -> { Place place = findOrSavePlace(routeDTO); - RouteItem routeItem = RouteItemConverter.toRouteItem(routeDTO, place, route); + RouteItem routeItem = RouteItemConverter.toRouteItem(routeDTO.getOrder(), place); + routeItem.setRoute(route); associateAnimationWithPlaceOrEvent(place, animation, getItemType(place)); return routeItem; }) @@ -89,13 +90,13 @@ private Place findOrSavePlace(ReviewRequestDTO.RouteDTO routeDTO) { } // 장소인지 이벤트인지 확인 - private ItemType getItemType(Place place) { - return eventLocationRepository.existsByLatitudeAndLongitude(place.getLat().toString(), place.getLng().toString()) ? ItemType.EVENT : ItemType.PLACE; + private ReviewType getItemType(Place place) { + return eventLocationRepository.existsByLatitudeAndLongitude(place.getLat().toString(), place.getLng().toString()) ? ReviewType.EVENT : ReviewType.PLACE; } // 장소 또는 이벤트에 애니메이션을 연결 - private void associateAnimationWithPlaceOrEvent(Place place, Animation animation, ItemType itemType) { - if (itemType == ItemType.PLACE) { + private void associateAnimationWithPlaceOrEvent(Place place, Animation animation, ReviewType itemType) { + if (itemType == ReviewType.PLACE) { placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation)); } else { // Place에 해당하는 Event 찾기 @@ -121,11 +122,11 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO List routeItems = routeItemRepository.findByRouteId(route.getId()); List places = routeItems.stream() - .map(routeItem -> placeRepository.findById(routeItem.getItemId()) + .map(routeItem -> placeRepository.findById(routeItem.getPlace().getId()) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND))) .collect(Collectors.toList()); - if (request.getReviewType() == ReviewRequestDTO.ItemType.PLACE) { + if (request.getReviewType() == ReviewType.PLACE) { // 먼저 PlaceReview를 저장 PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); placeReview = placeReviewRepository.save(placeReview); @@ -138,10 +139,10 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO placeReview.setPlaceList(placeReviewPlaces); placeReview = placeReviewRepository.save(placeReview); - imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ItemType.PLACE); + imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ReviewType.PLACE); return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); - } else if (request.getReviewType() == ReviewRequestDTO.ItemType.EVENT) { + } else if (request.getReviewType() == ReviewType.EVENT) { // 먼저 EventReview를 저장 EventReview eventReview = ReviewConverter.toEventReview(request, user, new ArrayList<>(), route); eventReview = eventReviewRepository.save(eventReview); @@ -154,7 +155,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO eventReview.setPlaceList(eventReviewPlaces); eventReview = eventReviewRepository.save(eventReview); - imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ItemType.EVENT); + imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ReviewType.EVENT); return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); } else { diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index 693ac1ab..c3a5fbed 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -1,15 +1,9 @@ package com.otakumap.domain.route.converter; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.converter.RouteItemConverter; import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.enums.ItemType; - -import java.util.List; -import com.otakumap.domain.route_item.entity.RouteItem; import java.util.List; diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index 384fe961..56db07bb 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -1,12 +1,8 @@ package com.otakumap.domain.route_item.converter; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.reviews.dto.ReviewRequestDTO; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.enums.ItemType; public class RouteItemConverter { public static RouteItem toRouteItem(Integer itemOrder, Place place) { diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 92b7a129..7984235c 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -5,12 +5,16 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor +@DynamicInsert +@DynamicUpdate public class RouteItem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) From 4d49129d2cdd7b1dafc43807098d854fcb46f3c3 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 22:49:20 +0900 Subject: [PATCH 338/516] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=ED=95=9C?= =?UTF-8?q?=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=9D=91=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/PlaceShortReview.java | 4 ++ .../DTO/UserReactionRequestDTO.java | 14 +++++ .../DTO/UserReactionResponseDTO.java | 20 +++++++ .../controller/UserReactionController.java | 28 +++++++++ .../converter/UserReactionConverter.java | 34 +++++++++++ .../user_reaction/entity/UserReaction.java | 37 ++++++++++++ .../repository/UserReactionRepository.java | 10 ++++ .../service/UserReactionCommandService.java | 8 +++ .../UserReactionCommandServiceImpl.java | 58 +++++++++++++++++++ 9 files changed, 213 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/controller/UserReactionController.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/converter/UserReactionConverter.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/repository/UserReactionRepository.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandService.java create mode 100644 src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java index 63dcb3c5..61836749 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java +++ b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java @@ -40,4 +40,8 @@ public class PlaceShortReview extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; + + public void updateLikes(Long likes) { this.likes = likes; } + + public void updateDislikes(Long dislikes) { this.dislikes = dislikes; } } diff --git a/src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionRequestDTO.java b/src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionRequestDTO.java new file mode 100644 index 00000000..23cfd954 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionRequestDTO.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.user_reaction.DTO; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.Getter; + +public class UserReactionRequestDTO { + @Getter + public static class ReactionRequestDTO { + @Min(0) + @Max(1) + private int reactionType; + } +} diff --git a/src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionResponseDTO.java b/src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionResponseDTO.java new file mode 100644 index 00000000..dc50cd59 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/DTO/UserReactionResponseDTO.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.user_reaction.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class UserReactionResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReactionResponseDTO { + private Long reviewId; + private Long likes; + private Long dislikes; + private Boolean isLiked; + private Boolean isDisliked; + } +} diff --git a/src/main/java/com/otakumap/domain/user_reaction/controller/UserReactionController.java b/src/main/java/com/otakumap/domain/user_reaction/controller/UserReactionController.java new file mode 100644 index 00000000..7467325c --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/controller/UserReactionController.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.user_reaction.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user_reaction.DTO.UserReactionResponseDTO; +import com.otakumap.domain.user_reaction.converter.UserReactionConverter; +import com.otakumap.domain.user_reaction.entity.UserReaction; +import com.otakumap.domain.user_reaction.service.UserReactionCommandService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class UserReactionController { + private final UserReactionCommandService userReactionCommandService; + + @PostMapping("/places/{placeId}/short-reviews/{reviewId}/reaction") + @Operation(summary = "명소 한줄 리뷰에 좋아요/싫어요 남기기 및 취소하기", description = "0을 요청하면 dislike, 1을 요청하면 like이며, 이미 존재하는 반응을 요청하면 취소됩니다.") + public ApiResponse reactToReview( + @CurrentUser User user, @PathVariable Long placeId, @PathVariable Long reviewId, @Valid @RequestBody int reactionType) { + UserReaction userReaction = userReactionCommandService.reactToReview(user, reviewId, reactionType); + return ApiResponse.onSuccess(UserReactionConverter.toReactionResponseDTO(userReaction.getPlaceShortReview(), userReaction)); + } +} diff --git a/src/main/java/com/otakumap/domain/user_reaction/converter/UserReactionConverter.java b/src/main/java/com/otakumap/domain/user_reaction/converter/UserReactionConverter.java new file mode 100644 index 00000000..c0e7138f --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/converter/UserReactionConverter.java @@ -0,0 +1,34 @@ +package com.otakumap.domain.user_reaction.converter; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user_reaction.DTO.UserReactionResponseDTO; +import com.otakumap.domain.user_reaction.entity.UserReaction; + +public class UserReactionConverter { + public static UserReactionResponseDTO.ReactionResponseDTO toReactionResponseDTO(PlaceShortReview placeShortReview, UserReaction userReaction) { + return UserReactionResponseDTO.ReactionResponseDTO.builder() + .reviewId(placeShortReview.getId()) + .likes(placeShortReview.getLikes()) + .dislikes(placeShortReview.getDislikes()) + .isLiked(userReaction.isLiked()) + .isDisliked(userReaction.isDisliked()) + .build(); + } + + public static UserReaction toLike(User user, PlaceShortReview placeShortReview, boolean isLiked) { + return UserReaction.builder() + .user(user) + .placeShortReview(placeShortReview) + .isLiked(isLiked) + .build(); + } + + public static UserReaction toDislike(User user, PlaceShortReview placeShortReview, boolean isDisliked) { + return UserReaction.builder() + .user(user) + .placeShortReview(placeShortReview) + .isDisliked(isDisliked) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java b/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java new file mode 100644 index 00000000..b2bf1107 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java @@ -0,0 +1,37 @@ +package com.otakumap.domain.user_reaction.entity; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "user_reaction", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "place_short_review_id"})}) +public class UserReaction extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_short_review_id", nullable = false) + private PlaceShortReview placeShortReview; + + @Column(nullable = false) + private boolean isLiked; + + @Column(nullable = false) + private boolean isDisliked; + + public void updateLiked(boolean isLiked) { this.isLiked = isLiked; } + + public void updateDisliked(boolean isDisliked) { this.isDisliked = isDisliked; } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user_reaction/repository/UserReactionRepository.java b/src/main/java/com/otakumap/domain/user_reaction/repository/UserReactionRepository.java new file mode 100644 index 00000000..5441283c --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/repository/UserReactionRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.user_reaction.repository; + +import com.otakumap.domain.user_reaction.entity.UserReaction; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserReactionRepository extends JpaRepository { + Optional findByUserIdAndPlaceShortReviewId(Long userId, Long placeShortReviewId); +} diff --git a/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandService.java b/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandService.java new file mode 100644 index 00000000..ebe15429 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.user_reaction.service; + +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user_reaction.entity.UserReaction; + +public interface UserReactionCommandService { + UserReaction reactToReview(User user, Long reviewId, int reactionType); +} diff --git a/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java new file mode 100644 index 00000000..f2323b12 --- /dev/null +++ b/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java @@ -0,0 +1,58 @@ +package com.otakumap.domain.user_reaction.service; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user_reaction.converter.UserReactionConverter; +import com.otakumap.domain.user_reaction.entity.UserReaction; +import com.otakumap.domain.user_reaction.repository.UserReactionRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserReactionCommandServiceImpl implements UserReactionCommandService { + private final UserReactionRepository userReactionRepository; + private final PlaceShortReviewRepository placeShortReviewRepository; + + @Override + @Transactional + public UserReaction reactToReview(User user, Long reviewId, int reactionType) { + PlaceShortReview placeShortReview = placeShortReviewRepository.findById(reviewId).orElseThrow(() -> new ReviewHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); + + UserReaction userReaction = userReactionRepository.findByUserIdAndPlaceShortReviewId(user.getId(), reviewId).orElseGet(() -> null); + + if (reactionType == 0) { // dislike + if (userReaction == null || (!userReaction.isLiked() && !userReaction.isDisliked())) { // 원래 아무 반응도 없었던 경우 + userReaction = UserReactionConverter.toDislike(user, placeShortReview, true); + placeShortReview.updateDislikes(placeShortReview.getDislikes() + 1); + } else if (userReaction.isDisliked()) { // 원래 싫어요가 있었던 경우 + userReaction.updateDisliked(false); + placeShortReview.updateDislikes(placeShortReview.getDislikes() - 1); + } else { // 원래 좋아요가 있었던 경우 + userReaction.updateDisliked(true); + userReaction.updateLiked(false); + placeShortReview.updateDislikes(placeShortReview.getDislikes() + 1); + placeShortReview.updateLikes(placeShortReview.getLikes() - 1); + } + } else { // like + if (userReaction == null || (!userReaction.isLiked() && !userReaction.isDisliked())) { + userReaction = UserReactionConverter.toLike(user, placeShortReview, true); + placeShortReview.updateLikes(placeShortReview.getLikes() + 1); + } else if (userReaction.isLiked()) { + userReaction.updateLiked(false); + placeShortReview.updateLikes(placeShortReview.getLikes() - 1); + } else { + userReaction.updateLiked(true); + userReaction.updateDisliked(false); + placeShortReview.updateLikes(placeShortReview.getLikes() + 1); + placeShortReview.updateDislikes(placeShortReview.getDislikes() - 1); + } + } + placeShortReviewRepository.save(placeShortReview); + return userReactionRepository.save(userReaction); + } +} \ No newline at end of file From bf7bf6c6fc99db5a1382a26af5d1ce2fafe8cb2b Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 10 Feb 2025 22:56:27 +0900 Subject: [PATCH 339/516] =?UTF-8?q?Fix:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=80=EC=9E=A5=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserReactionCommandServiceImpl.java | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java index f2323b12..51891445 100644 --- a/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user_reaction/service/UserReactionCommandServiceImpl.java @@ -23,35 +23,44 @@ public class UserReactionCommandServiceImpl implements UserReactionCommandServic public UserReaction reactToReview(User user, Long reviewId, int reactionType) { PlaceShortReview placeShortReview = placeShortReviewRepository.findById(reviewId).orElseThrow(() -> new ReviewHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); - UserReaction userReaction = userReactionRepository.findByUserIdAndPlaceShortReviewId(user.getId(), reviewId).orElseGet(() -> null); + UserReaction userReaction = userReactionRepository.findByUserIdAndPlaceShortReviewId(user.getId(), reviewId).orElse(null); - if (reactionType == 0) { // dislike - if (userReaction == null || (!userReaction.isLiked() && !userReaction.isDisliked())) { // 원래 아무 반응도 없었던 경우 + if (userReaction == null) { + if (reactionType == 0) { // dislike userReaction = UserReactionConverter.toDislike(user, placeShortReview, true); placeShortReview.updateDislikes(placeShortReview.getDislikes() + 1); - } else if (userReaction.isDisliked()) { // 원래 싫어요가 있었던 경우 - userReaction.updateDisliked(false); - placeShortReview.updateDislikes(placeShortReview.getDislikes() - 1); - } else { // 원래 좋아요가 있었던 경우 - userReaction.updateDisliked(true); - userReaction.updateLiked(false); - placeShortReview.updateDislikes(placeShortReview.getDislikes() + 1); - placeShortReview.updateLikes(placeShortReview.getLikes() - 1); - } - } else { // like - if (userReaction == null || (!userReaction.isLiked() && !userReaction.isDisliked())) { + } else { // like userReaction = UserReactionConverter.toLike(user, placeShortReview, true); placeShortReview.updateLikes(placeShortReview.getLikes() + 1); - } else if (userReaction.isLiked()) { - userReaction.updateLiked(false); - placeShortReview.updateLikes(placeShortReview.getLikes() - 1); - } else { - userReaction.updateLiked(true); - userReaction.updateDisliked(false); - placeShortReview.updateLikes(placeShortReview.getLikes() + 1); - placeShortReview.updateDislikes(placeShortReview.getDislikes() - 1); + } + } else { + if (reactionType == 0) { // dislike + if (!userReaction.isDisliked()) { + userReaction.updateDisliked(true); + userReaction.updateLiked(false); + placeShortReview.updateDislikes(placeShortReview.getDislikes() + 1); + if (userReaction.isLiked()) { + placeShortReview.updateLikes(placeShortReview.getLikes() - 1); + } + } else { + userReaction.updateDisliked(false); + placeShortReview.updateDislikes(placeShortReview.getDislikes() - 1); + } + } else { // like + if (!userReaction.isLiked()) { + userReaction.updateLiked(true); + userReaction.updateDisliked(false); + placeShortReview.updateLikes(placeShortReview.getLikes() + 1); + if (userReaction.isDisliked()) { + placeShortReview.updateDislikes(placeShortReview.getDislikes() - 1); + } + } else { + userReaction.updateLiked(false); + placeShortReview.updateLikes(placeShortReview.getLikes() - 1); + } } } + placeShortReviewRepository.save(placeShortReview); return userReactionRepository.save(userReaction); } From c826b2c570fe5e30b8d3223b248fd3d0a331ed5f Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:30:24 +0900 Subject: [PATCH 340/516] =?UTF-8?q?Feat:=20AnimationConverter,=20Animation?= =?UTF-8?q?ResponseDTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/AnimationConverter.java | 20 +++++++++++++++++ .../animation/dto/AnimationResponseDTO.java | 22 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java create mode 100644 src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java new file mode 100644 index 00000000..aa397184 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.animation.converter; + +import com.otakumap.domain.animation.dto.AnimationResponseDTO; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +import com.otakumap.domain.mapping.PlaceAnimation; + +import java.util.List; + +public class AnimationConverter { + + public static AnimationResponseDTO.AnimationInfoDTO toAnimationInfoDTO(PlaceAnimation placeAnimation, Boolean isFavorite, + List hashTags) { + return AnimationResponseDTO.AnimationInfoDTO.builder() + .animationId(placeAnimation.getAnimation().getId()) + .animationName(placeAnimation.getAnimation().getName()) + .isFavorite(isFavorite) + .hashTags(hashTags) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java new file mode 100644 index 00000000..5c88f6c9 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java @@ -0,0 +1,22 @@ +package com.otakumap.domain.animation.dto; + +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class AnimationResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationInfoDTO { + private Long animationId; + private String animationName; + private Boolean isFavorite; + private List hashTags; + } +} From 6206a577e3d636fe5bbc018d77257e15bba9617c Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:32:55 +0900 Subject: [PATCH 341/516] =?UTF-8?q?Refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/converter/EventConverter.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 04aebc5d..25aa8775 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -3,12 +3,10 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_location.converter.EventLocationConverter; -import com.otakumap.domain.hash_tag.converter.HashTagConverter; -import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; import java.util.List; -import java.util.stream.Collectors; public class EventConverter { @@ -38,15 +36,14 @@ public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { .build(); } - public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event event, Boolean isFavorite, List hashTags) { + public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event event, Boolean isFavorite, String animationTitle, List hashTags) { return EventResponseDTO.SearchedEventInfoDTO.builder() .eventId(event.getId()) .name(event.getName()) .isFavorite(isFavorite) - .hashTags(hashTags.stream() - .map(HashTagConverter::toHashTagDTO) - .collect(Collectors.toList())) + .animationTitle(animationTitle) + .hashTags(hashTags) .build(); } } From 59401d69d4f2faa986ae230a4ad8cf2e8f294cf3 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:34:28 +0900 Subject: [PATCH 342/516] =?UTF-8?q?Feat:=20animationTitle=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/event/dto/EventResponseDTO.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index f44ddcfc..e10253f0 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -51,6 +51,7 @@ public static class SearchedEventInfoDTO { Long eventId; String name; Boolean isFavorite; + private String animationTitle; List hashTags; } } From b841b79b885ff53716e76d684ec9a173e231b8e6 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:37:01 +0900 Subject: [PATCH 343/516] =?UTF-8?q?Feat:=20SearchedPlaceDTO=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80,=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20converter=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/DTO/PlaceResponseDTO.java | 11 +++++++++++ .../domain/place/converter/PlaceConverter.java | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index e1648287..37ca055e 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.DTO; +import com.otakumap.domain.animation.dto.AnimationResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -52,4 +53,14 @@ public static class PlaceDTO { private Double latitude; private Double longitude; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchedPlaceInfoDTO { + Long placeId; + String name; + List animations; + } } diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index 4a7e8810..06990f05 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.converter; +import com.otakumap.domain.animation.dto.AnimationResponseDTO; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.entity.Place; @@ -61,4 +62,14 @@ public static List toPlaceDTOList(List places) .map(PlaceConverter::toPlaceDTO) .collect(Collectors.toList()); } + + public static PlaceResponseDTO.SearchedPlaceInfoDTO toSearchedPlaceInfoDTO(Place place, + List animationDTOs) { + return PlaceResponseDTO.SearchedPlaceInfoDTO.builder() + .placeId(place.getId()) + .name(place.getName()) + .animations(animationDTOs) + .build(); + + } } From 6c0630097d3b7690b1465b0c46e6404cd18013b5 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:46:06 +0900 Subject: [PATCH 344/516] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=20=EC=9E=A5=EC=86=8C=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=8F=84=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/search/dto/SearchResponseDTO.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java b/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java index 94ec865b..14720d1a 100644 --- a/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java +++ b/src/main/java/com/otakumap/domain/search/dto/SearchResponseDTO.java @@ -1,6 +1,7 @@ package com.otakumap.domain.search.dto; import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -17,7 +18,8 @@ public class SearchResponseDTO { public static class SearchResultDTO { private Double latitude; private Double longitude; - private int eventCount; + private int count; private List events; + private List places; } } From 3d63a49ea30bc3c5e14c91ebaa8f9f216dfc5a3c Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:47:12 +0900 Subject: [PATCH 345/516] =?UTF-8?q?Feat:=20toSearchResultDTO=EC=97=90=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A0=95=EB=B3=B4=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/search/converter/SearchConverter.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java b/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java index c9169f0c..eb80b8db 100644 --- a/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java +++ b/src/main/java/com/otakumap/domain/search/converter/SearchConverter.java @@ -1,18 +1,26 @@ package com.otakumap.domain.search.converter; import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.search.dto.SearchResponseDTO; import java.util.List; public class SearchConverter { - public static SearchResponseDTO.SearchResultDTO toSearchResultDTO(List eventDTOs, Double lat, Double lng) { + public static SearchResponseDTO.SearchResultDTO toSearchResultDTO(List eventDTOs, + List placeDTOs, + Double lat, Double lng) { + int placeAniCount = placeDTOs.stream() + .mapToInt(placeDto -> placeDto.getAnimations().size()) + .sum(); + return SearchResponseDTO.SearchResultDTO.builder() .latitude(lat) .longitude(lng) - .eventCount(eventDTOs.size()) + .count(eventDTOs.size() + placeAniCount) .events(eventDTOs) + .places(placeDTOs) .build(); } } From b4261c4882e59499afc1cbaf147b80d25a19d098 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:55:45 +0900 Subject: [PATCH 346/516] =?UTF-8?q?Feat:=20findByLatAndLng=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/repository/PlaceRepository.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 5b787841..1c8e3924 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -3,5 +3,8 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface PlaceRepository extends JpaRepository { - } + List findByLatAndLng(Double lat, Double lng); +} From 663fece92ae4d9d25ee246f2159440d4142e0940 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:56:20 +0900 Subject: [PATCH 347/516] =?UTF-8?q?Feat:=20findByUserAndPlaceAnimation=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/repository/PlaceLikeRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index d5b42168..559244cd 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -11,4 +11,6 @@ public interface PlaceLikeRepository extends JpaRepository { boolean existsByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); // 특정 Place와 연결된 PlaceLike가 존재하는지 확인 Optional findByPlaceIdAndUserId(Long placeId, Long userId); + + Optional findByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); } \ No newline at end of file From 4bde51126ba3162b2278c9f11c7d972f831fe3e9 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:56:48 +0900 Subject: [PATCH 348/516] =?UTF-8?q?Feat:=20Service=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/service/SearchServiceImpl.java | 130 +++++++++++++----- 1 file changed, 93 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index e1646520..5e82fe77 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -1,5 +1,7 @@ package com.otakumap.domain.search.service; +import com.otakumap.domain.animation.converter.AnimationConverter; +import com.otakumap.domain.animation.dto.AnimationResponseDTO; import com.otakumap.domain.event.converter.EventConverter; import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; @@ -8,10 +10,15 @@ import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.event_location.entity.EventLocation; import com.otakumap.domain.event_location.repository.EventLocationRepository; -import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.mapping.EventHashTag; import com.otakumap.domain.mapping.repository.EventHashTagRepository; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.converter.PlaceConverter; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.search.converter.SearchConverter; import com.otakumap.domain.search.dto.SearchResponseDTO; import com.otakumap.domain.search.repository.SearchRepositoryCustom; @@ -20,10 +27,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -35,6 +39,8 @@ public class SearchServiceImpl implements SearchService { private final EventLocationRepository eventLocationRepository; private final EventLikeRepository eventLikeRepository; private final EventHashTagRepository eventHashTagRepository; + private final PlaceLikeRepository placeLikeRepository; + private final PlaceRepository placeRepository; @Transactional(readOnly = true) @Override @@ -59,40 +65,90 @@ public List getSearchedResult (User user, Str .distinct() // 중복 이벤트 제거 .toList(); - // 위도,경도 조합으로 같은 것들끼리 그룹화 + // 이벤트를 위치(위도, 경도) 기준으로 그룹화 Map> groupedEvents = combinedEvents.stream() .filter(event -> event.getEventLocation() != null) - .collect(Collectors.groupingBy(event -> { - Double lat = event.getEventLocation().getLat(); - Double lng = event.getEventLocation().getLng(); - return lat + "," + lng; - })); - - return groupedEvents.entrySet().stream() - .map(entry -> { - String key = entry.getKey(); // "lat,lng" 형태의 키 값에서 위/경도 추출 - String[] parts = key.split(","); - Double lat = Double.valueOf(parts[0]); - Double lng = Double.valueOf(parts[1]); - - // 각 event를 DTO로 변환 - List eventDTOs = entry.getValue().stream() - .map(event -> { - EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); - boolean isFavorite = eventLike != null && eventLike.getIsFavorite() == Boolean.TRUE; - - List eventHashTags = eventHashTagRepository.findByEvent(event); - List hashTags = eventHashTags.stream() - .map(EventHashTag::getHashTag) - .collect(Collectors.toList()); - - return EventConverter.toSearchedEventInfoDTO(event, isFavorite, hashTags); - }) - .collect(Collectors.toList()); - - return SearchConverter.toSearchResultDTO(eventDTOs, lat, lng); - }) - .toList(); + .collect(Collectors.groupingBy(event -> + event.getEventLocation().getLat() + "," + event.getEventLocation().getLng() + )); + + // 장소들을 위치 기준으로 그룹화 (같은 위치에 여러 명소가 있을 수 있으므로 그룹화) + Map> groupedPlaces = safePlaces.stream() + .collect(Collectors.groupingBy(place -> place.getLat() + "," + place.getLng())); + + // 만약 groupedEvents에는 존재하는 위치(key)가 groupedPlaces에 없다면, + // 해당 위치의 장소들을 조회해서 groupedPlaces에 추가 + for (String key : groupedEvents.keySet()) { + if (!groupedPlaces.containsKey(key)) { + String[] parts = key.split(","); + Double lat = Double.valueOf(parts[0]); + Double lng = Double.valueOf(parts[1]); + + List additionalPlaces = placeRepository.findByLatAndLng(lat, lng); + + if (additionalPlaces != null && !additionalPlaces.isEmpty()) { + groupedPlaces.put(key, additionalPlaces); + } + } + } + + // 모든 위치 키의 합집합 + Set allKeys = new HashSet<>(); + allKeys.addAll(groupedEvents.keySet()); + allKeys.addAll(groupedPlaces.keySet()); + + // 각 위치 키별로 이벤트와 장소 정보를 DTO로 변환하여 SearchResultDTO 생성 + return allKeys.stream().map(key -> { + String[] parts = key.split(","); + Double lat = Double.valueOf(parts[0]); + Double lng = Double.valueOf(parts[1]); + + // 해당 위치의 이벤트들을 DTO로 변환 + List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) + .stream().map(event -> { + EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); + boolean isFavorite = (eventLike != null && eventLike.getIsFavorite() == Boolean.TRUE); + + // 이벤트에 연결된 해시태그 조회 + List eventHashTags = eventHashTagRepository.findByEvent(event); + List hashTags = eventHashTags.stream() + .map(eht -> HashTagConverter.toHashTagDTO(eht.getHashTag())) + .collect(Collectors.toList()); + + // eventAnimationList를 이용해 애니메이션 제목을 추출 (여러 개가 있을 경우 콤마로 연결) + String animationTitle = event.getEventAnimationList().stream() + .map(ea -> ea.getAnimation().getName()) + .collect(Collectors.joining(", ")); + + return EventConverter.toSearchedEventInfoDTO(event, isFavorite, animationTitle, hashTags); + }) + .collect(Collectors.toList()); + + // 장소 DTO 변환 + List placeDTOs = groupedPlaces.getOrDefault(key, Collections.emptyList()) + .stream().map(place -> { + // 각 장소의 모든 PlaceAnimation을 AnimationInfoDTO 리스트로 변환 (애니메이션별 좋아요 여부 계산) + List animationDTOs = place.getPlaceAnimationList().stream() + .map(placeAnimation -> { + // 각 장소의 애니메이션별 좋아요 여부 + boolean animationIsFavorite = placeLikeRepository.findByUserAndPlaceAnimation(user, placeAnimation) + .map(pl -> Boolean.TRUE.equals(pl.getIsFavorite())) + .orElse(false); + + List hashTags = placeAnimation.getPlaceAnimationHashTags().stream() + .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())) + .collect(Collectors.toList()); + + return AnimationConverter.toAnimationInfoDTO(placeAnimation, animationIsFavorite, hashTags); + }) + .collect(Collectors.toList()); + + return PlaceConverter.toSearchedPlaceInfoDTO(place, animationDTOs); + }) + .toList(); + + return SearchConverter.toSearchResultDTO(eventDTOs, placeDTOs, lat, lng); + }).collect(Collectors.toList()); } } From b2f313b5e145402c82383dd9494a729454a29593 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:25:34 +0900 Subject: [PATCH 349/516] =?UTF-8?q?Feat:=20isLiked=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/converter/EventConverter.java | 2 +- .../otakumap/domain/event/dto/EventResponseDTO.java | 2 +- .../domain/search/service/SearchServiceImpl.java | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 25aa8775..40b25f97 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -41,7 +41,7 @@ public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event return EventResponseDTO.SearchedEventInfoDTO.builder() .eventId(event.getId()) .name(event.getName()) - .isFavorite(isFavorite) + .isLiked(isFavorite) .animationTitle(animationTitle) .hashTags(hashTags) .build(); diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index e10253f0..ecfec56c 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -50,7 +50,7 @@ public static class EventDetailDTO { public static class SearchedEventInfoDTO { Long eventId; String name; - Boolean isFavorite; + Boolean isLiked; private String animationTitle; List hashTags; } diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index 5e82fe77..3bb99ddc 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -107,7 +107,7 @@ public List getSearchedResult (User user, Str List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) .stream().map(event -> { EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); - boolean isFavorite = (eventLike != null && eventLike.getIsFavorite() == Boolean.TRUE); + boolean isLiked = (eventLike != null); // 이벤트에 연결된 해시태그 조회 List eventHashTags = eventHashTagRepository.findByEvent(event); @@ -120,7 +120,7 @@ public List getSearchedResult (User user, Str .map(ea -> ea.getAnimation().getName()) .collect(Collectors.joining(", ")); - return EventConverter.toSearchedEventInfoDTO(event, isFavorite, animationTitle, hashTags); + return EventConverter.toSearchedEventInfoDTO(event, isLiked, animationTitle, hashTags); }) .collect(Collectors.toList()); @@ -131,15 +131,13 @@ public List getSearchedResult (User user, Str List animationDTOs = place.getPlaceAnimationList().stream() .map(placeAnimation -> { // 각 장소의 애니메이션별 좋아요 여부 - boolean animationIsFavorite = placeLikeRepository.findByUserAndPlaceAnimation(user, placeAnimation) - .map(pl -> Boolean.TRUE.equals(pl.getIsFavorite())) - .orElse(false); + boolean isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); List hashTags = placeAnimation.getPlaceAnimationHashTags().stream() .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())) .collect(Collectors.toList()); - return AnimationConverter.toAnimationInfoDTO(placeAnimation, animationIsFavorite, hashTags); + return AnimationConverter.toAnimationInfoDTO(placeAnimation, isLiked, hashTags); }) .collect(Collectors.toList()); From 6b01cba3bf23abb8a23d0ebc82ea4f413255d246 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 11 Feb 2025 22:40:41 +0900 Subject: [PATCH 350/516] =?UTF-8?q?Fix:=20=EC=A2=85=EB=A3=8C=EC=9D=BC?= =?UTF-8?q?=EC=9E=90=EA=B0=80=20=EB=A8=BC=20=EB=82=A0=EC=A7=9C=EB=B6=80?= =?UTF-8?q?=ED=84=B0=20=EB=82=98=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/repository/EventRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index a4398462..026ea5b9 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -143,7 +143,7 @@ private Page paginateSearchEventResults(Integer page, List events = queryFactory.selectFrom(event) .where(searchCondition) .fetch(); - events.sort(Comparator.comparing(Event::getEndDate) + events.sort(Comparator.comparing(Event::getEndDate).reversed() .thenComparing(Event::getTitle)); From 7d38ee3fcb40377c1ae44fac4b8ad9b321f178f9 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 11 Feb 2025 23:04:05 +0900 Subject: [PATCH 351/516] =?UTF-8?q?Fix:=20conflict=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/event/dto/EventResponseDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index 5a0aabe1..e0991ed9 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -63,6 +63,6 @@ public static class SearchedEventInfoDTO { String name; Boolean isLiked; private String animationTitle; - List hashTags + List hashTags; } } From a2f8f6269d9d9d3551709bb537604342214286b5 Mon Sep 17 00:00:00 2001 From: haerr <144093044+haerxeong@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:07:05 +0900 Subject: [PATCH 352/516] =?UTF-8?q?Revert=20"Feat:=20=ED=9B=84=EA=B8=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20API=20=EA=B5=AC=ED=98=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AnimationController.java | 51 ------ .../converter/AnimationConverter.java | 33 ---- .../animation/dto/AnimationRequestDTO.java | 12 -- .../animation/dto/AnimationResponseDTO.java | 29 --- .../domain/animation/entity/Animation.java | 2 +- .../repository/AnimationRepository.java | 6 - .../service/AnimationCommandService.java | 7 - .../service/AnimationCommandServiceImpl.java | 43 ----- .../service/AnimationQueryService.java | 5 - .../service/AnimationQueryServiceImpl.java | 10 -- .../repository/EventAnimationRepository.java | 7 - .../repository/EventLocationRepository.java | 4 - .../event_review/entity/EventReview.java | 8 +- .../EventReviewPlaceRepository.java | 7 - .../otakumap/domain/image/entity/Image.java | 4 - .../image/service/ImageCommandService.java | 3 +- .../service/ImageCommandServiceImpl.java | 24 +-- .../domain/mapping/EventReviewPlace.java | 28 --- .../domain/mapping/PlaceReviewPlace.java | 30 ---- .../otakumap/domain/place/entity/Place.java | 10 +- .../place/repository/PlaceRepository.java | 5 +- .../repository/PlaceAnimationRepository.java | 2 +- .../controller/PlaceReviewController.java | 13 ++ .../converter/PlaceReviewConverter.java | 24 ++- .../dto/PlaceReviewRequestDTO.java | 19 ++ .../dto/PlaceReviewResponseDTO.java | 13 +- .../place_review/entity/PlaceReview.java | 14 +- .../repository/PlaceReviewRepositoryImpl.java | 8 +- .../service/PlaceReviewCommandService.java | 5 + .../PlaceReviewCommandServiceImpl.java | 26 +++ .../PlaceReviewPlaceRepository.java | 7 - .../reviews/controller/ReviewController.java | 18 -- .../reviews/converter/ReviewConverter.java | 92 +--------- .../domain/reviews/dto/ReviewRequestDTO.java | 47 ----- .../domain/reviews/dto/ReviewResponseDTO.java | 10 -- .../repository/ReviewRepositoryImpl.java | 2 +- .../reviews/service/ReviewCommandService.java | 10 -- .../service/ReviewCommandServiceImpl.java | 166 ------------------ .../route/converter/RouteConverter.java | 19 +- .../domain/route_item/entity/RouteItem.java | 4 - .../repository/RouteItemRepository.java | 2 - .../apiPayload/code/status/ErrorStatus.java | 10 +- .../exception/handler/AnimationHandler.java | 8 - 43 files changed, 126 insertions(+), 721 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/animation/controller/AnimationController.java delete mode 100644 src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java delete mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java delete mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java delete mode 100644 src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java delete mode 100644 src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java delete mode 100644 src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java delete mode 100644 src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java create mode 100644 src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java delete mode 100644 src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java delete mode 100644 src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java delete mode 100644 src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java delete mode 100644 src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java delete mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java diff --git a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java deleted file mode 100644 index 4e1e40a5..00000000 --- a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.otakumap.domain.animation.controller; - -import com.otakumap.domain.animation.dto.AnimationRequestDTO; -import com.otakumap.domain.animation.dto.AnimationResponseDTO; -import com.otakumap.domain.animation.converter.AnimationConverter; -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.animation.service.AnimationCommandService; -import com.otakumap.domain.animation.service.AnimationQueryService; -import com.otakumap.global.apiPayload.ApiResponse; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.SearchHandler; -import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/animations") -public class AnimationController { - private final AnimationQueryService animationQueryService; - private final AnimationCommandService animationCommandService; - - @GetMapping("/search") - @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다. 공백은 허용되지 않습니다.") - public ApiResponse searchAnimation( - @RequestParam @NotBlank(message = "검색어를 입력해주세요") - @Pattern(regexp = "^[^\\s].*$", message = "첫 글자는 공백이 될 수 없습니다") String keyword) { - List animationList = animationQueryService.searchAnimation(keyword); - - if (animationList.isEmpty()) { - throw new SearchHandler(ErrorStatus.ANIMATION_NOT_FOUND); - } - - return ApiResponse.onSuccess(AnimationConverter.animationResultListDTO(animationList)); - } - - @PostMapping - @Operation(summary = "애니메이션 등록", description = "원하는 애니메이션이 없을 경우, 사용자가 애니메이션을 직접 등록합니다.") - public ApiResponse createAnimation( - @RequestBody @Valid AnimationRequestDTO.AnimationCreationRequestDTO request) { - Animation animation = animationCommandService.createAnimation(request.getName()); - return ApiResponse.onSuccess(AnimationConverter.toAnimationCreationResponseDTO(animation)); - } -} diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java index 2d7fc0e3..aa397184 100644 --- a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -1,7 +1,6 @@ package com.otakumap.domain.animation.converter; import com.otakumap.domain.animation.dto.AnimationResponseDTO; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.mapping.PlaceAnimation; @@ -18,36 +17,4 @@ public static AnimationResponseDTO.AnimationInfoDTO toAnimationInfoDTO(PlaceAnim .hashTags(hashTags) .build(); } - - public static AnimationResponseDTO.AnimationResultDTO animationResultDTO(Animation animation) { - return AnimationResponseDTO.AnimationResultDTO.builder() - .animationId(animation.getId()) - .name(animation.getName()) - .build(); - } - - public static AnimationResponseDTO.AnimationResultListDTO animationResultListDTO(List animations) { - List animationResultDTOs = animations.stream() - .map(AnimationConverter::animationResultDTO) - .toList(); - - return AnimationResponseDTO.AnimationResultListDTO.builder() - .animations(animationResultDTOs) - .listSize(animationResultDTOs.size()) - .build(); - } - - public static AnimationResponseDTO.AnimationCreationResponseDTO toAnimationCreationResponseDTO(Animation animation) { - return AnimationResponseDTO.AnimationCreationResponseDTO.builder() - .animationId(animation.getId()) - .name(animation.getName()) - .createdAt(animation.getCreatedAt()) - .build(); - } - - public static Animation toAnimation(String name) { - return Animation.builder() - .name(name) - .build(); - } } diff --git a/src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java b/src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java deleted file mode 100644 index 28739413..00000000 --- a/src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.otakumap.domain.animation.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; - -public class AnimationRequestDTO { - @Getter - public static class AnimationCreationRequestDTO { - @NotBlank - String name; - } -} diff --git a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java index a7eb117d..5c88f6c9 100644 --- a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; import java.util.List; public class AnimationResponseDTO { @@ -20,32 +19,4 @@ public static class AnimationInfoDTO { private Boolean isFavorite; private List hashTags; } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class AnimationResultDTO { - Long animationId; - String name; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class AnimationResultListDTO { - List animations; - Integer listSize; - } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class AnimationCreationResponseDTO { - Long animationId; - String name; - LocalDateTime createdAt; - } } diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index cd853f0e..1259b0c9 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -15,6 +15,6 @@ public class Animation extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 50) + @Column(nullable = false, length = 20) private String name; } diff --git a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java index f90e98f1..6977cd99 100644 --- a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java +++ b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java @@ -2,12 +2,6 @@ import com.otakumap.domain.animation.entity.Animation; 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 AnimationRepository extends JpaRepository { - @Query("SELECT a FROM Animation a WHERE REPLACE(LOWER(a.name), ' ', '') LIKE CONCAT('%', REPLACE(LOWER(:keyword), ' ', ''), '%')") - List searchAnimationByKeyword(@Param("keyword") String keyword); } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java deleted file mode 100644 index 1ebbc25d..00000000 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.otakumap.domain.animation.service; - -import com.otakumap.domain.animation.entity.Animation; - -public interface AnimationCommandService { - Animation createAnimation(String name); -} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java deleted file mode 100644 index bc6778a0..00000000 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.otakumap.domain.animation.service; - -import com.otakumap.domain.animation.converter.AnimationConverter; -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.animation.repository.AnimationRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class AnimationCommandServiceImpl implements AnimationCommandService { - private final AnimationRepository animationRepository; - - @Override - @Transactional - public Animation createAnimation(String name) { - if (name == null || name.trim().isEmpty()) { - throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_IS_EMPTY); - } - - if (name.length() < 2 || name.length() > 50) { - throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_LENGTH); - } - - if (!name.matches("^[가-힣a-zA-Z0-9\\\\s./()-]+$")) { - throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_SPECIAL_CHARACTER); - } - - String normalizedName = name.replaceAll("\\s+", "").toLowerCase(); - - // 중복 검사 - if (animationRepository.findAll().stream() - .anyMatch(animation -> animation.getName().replaceAll("\\s+", "").toLowerCase().equals(normalizedName))) { - throw new AnimationHandler(ErrorStatus.ANIMATION_ALREADY_EXISTS); - } - - Animation newAnimation = AnimationConverter.toAnimation(name); - return animationRepository.save(newAnimation); - } -} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java index 6070b6f8..b569c48e 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java @@ -1,10 +1,5 @@ package com.otakumap.domain.animation.service; -import com.otakumap.domain.animation.entity.Animation; - -import java.util.List; - public interface AnimationQueryService { - List searchAnimation(String keyword); boolean existsById(Long animationId); } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java index 4bf5d88e..b7896104 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -1,13 +1,9 @@ package com.otakumap.domain.animation.service; -import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.repository.AnimationRepository; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class AnimationQueryServiceImpl implements AnimationQueryService { @@ -17,10 +13,4 @@ public class AnimationQueryServiceImpl implements AnimationQueryService { public boolean existsById(Long animationId) { return animationRepository.existsById(animationId); } - - @Override - @Transactional - public List searchAnimation(String keyword) { - return animationRepository.searchAnimationByKeyword(keyword); - } } diff --git a/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java b/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java deleted file mode 100644 index ea70b12c..00000000 --- a/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.otakumap.domain.event_animation.repository; - -import com.otakumap.domain.mapping.EventAnimation; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface EventAnimationRepository extends JpaRepository { -} diff --git a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java index e64a9147..93ae5ca8 100644 --- a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java +++ b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java @@ -1,14 +1,10 @@ package com.otakumap.domain.event_location.repository; -import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_location.entity.EventLocation; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -import java.util.Optional; public interface EventLocationRepository extends JpaRepository { - boolean existsByLatitudeAndLongitude(String lat, String lng); - Optional findByLatitudeAndLongitude(String lat, String lng); List findByLatAndLng(Double latitude, Double longitude); } diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index c93da32d..d915b57f 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -3,7 +3,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; -import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -27,7 +26,7 @@ public class EventReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(length = 50, nullable = false) + @Column(length = 20, nullable = false) private String title; @Column(columnDefinition = "text not null") @@ -46,9 +45,6 @@ public class EventReview extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") private List images = new ArrayList<>(); - @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL) - private List placeList = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; @@ -64,6 +60,4 @@ public class EventReview extends BaseEntity { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; - - public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java deleted file mode 100644 index ab386f0c..00000000 --- a/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.otakumap.domain.event_review_place.repository; - -import com.otakumap.domain.mapping.EventReviewPlace; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface EventReviewPlaceRepository extends JpaRepository { -} diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java index c733d55a..91f5c9bf 100644 --- a/src/main/java/com/otakumap/domain/image/entity/Image.java +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -33,8 +33,4 @@ public class Image extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_review_id") private EventReview eventReview; - - public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } - - public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java index 7ab1825f..9df0bb58 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -1,14 +1,13 @@ package com.otakumap.domain.image.service; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.reviews.enums.ReviewType; import org.springframework.web.multipart.MultipartFile; import java.util.List; public interface ImageCommandService { Image uploadProfileImage(MultipartFile file, Long userId); - List uploadReviewImages(List files, Long reviewId, ReviewType reviewType); + List uploadReviewImages(List files, Long reviewId); Image uploadImage(MultipartFile file, String folder); } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java index 71d6a63e..f335b4ea 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -1,16 +1,10 @@ package com.otakumap.domain.image.service; -import com.otakumap.domain.event_review.entity.EventReview; -import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.repository.ImageRepository; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.ImageHandler; -import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.util.AmazonS3Util; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -25,8 +19,6 @@ public class ImageCommandServiceImpl implements ImageCommandService { private final ImageRepository imageRepository; private final AmazonS3Util amazonS3Util; - private final EventReviewRepository eventReviewRepository; - private final PlaceReviewRepository placeReviewRepository; @Override @Transactional @@ -39,25 +31,15 @@ public Image uploadProfileImage(MultipartFile file, Long userId) { @Override @Transactional - public List uploadReviewImages(List files, Long reviewId, ReviewType reviewType) { + public List uploadReviewImages(List files, Long reviewId) { return files.stream() .map(file -> { String keyName = amazonS3Util.generateReviewKeyName(); String fileUrl = amazonS3Util.uploadFile(keyName, file); - Image image = ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); - if (reviewType == ReviewType.EVENT) { - EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); - image.setEventReview(eventReview); - } else if (reviewType == ReviewType.PLACE) { - PlaceReview placeReview = placeReviewRepository.findById(reviewId) - .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); - image.setPlaceReview(placeReview); - } - - return imageRepository.save(image); + return ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); }) + .map(imageRepository::save) .collect(Collectors.toList()); } diff --git a/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java deleted file mode 100644 index f4f7805a..00000000 --- a/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.otakumap.domain.mapping; - -import com.otakumap.domain.event_review.entity.EventReview; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class EventReviewPlace extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "event_review_id") - private EventReview eventReview; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") - private Place place; - - public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } -} diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java deleted file mode 100644 index 070d2238..00000000 --- a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.otakumap.domain.mapping; - -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class PlaceReviewPlace extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_review_id") - private PlaceReview placeReview; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") - private Place place; - - public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } - - public void setPlace(Place place) { this.place = place; } -} diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 585b506c..bbb46eba 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,8 +1,6 @@ package com.otakumap.domain.place.entity; -import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.route_item.entity.RouteItem; import com.otakumap.global.common.BaseEntity; @@ -25,7 +23,7 @@ public class Place extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 50) + @Column(nullable = false, length = 20) private String name; @Column(nullable = false) @@ -47,12 +45,6 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) - private List placeReviewList = new ArrayList<>(); - - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) - private List eventReviewList = new ArrayList<>(); - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL, orphanRemoval = true) private List routeItems = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 6819f675..1c8e3924 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -3,11 +3,8 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - import java.util.List; public interface PlaceRepository extends JpaRepository { List findByLatAndLng(Double lat, Double lng); - Optional findByNameAndLatAndLng(String name, Double lat, Double lng); -} \ No newline at end of file +} diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index 8837a058..2875afb9 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -10,4 +10,4 @@ public interface PlaceAnimationRepository extends JpaRepository findByIdAndPlaceId(Long id, Long placeId); List findByPlaceId(Long placeId); Optional findByPlaceIdAndAnimationId(Long placeId, Long animationId); -} +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index 8d0a3680..f5df232f 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -1,11 +1,16 @@ package com.otakumap.domain.place_review.controller; +import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.service.PlaceReviewCommandService; import com.otakumap.domain.place_review.service.PlaceReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -13,8 +18,16 @@ @RequiredArgsConstructor @RequestMapping("/api") public class PlaceReviewController { + private final PlaceReviewCommandService placeReviewCommandService; private final PlaceReviewQueryService placeReviewQueryService; + @PostMapping("/review") + @Operation(summary = "리뷰 작성") + public ApiResponse createReview(@RequestBody @Valid PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { + PlaceReview placeReview = placeReviewCommandService.createReview(request); + return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); + } + @GetMapping("/places/{placeId}/reviews") @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") @Parameters({ diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 517f408e..48bf7a8d 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -4,23 +4,41 @@ import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.entity.User; import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; public class PlaceReviewConverter { + public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { + return PlaceReviewResponseDTO.ReviewCreateResponseDTO.builder() + .reviewId(placeReview.getId()) + .title(placeReview.getTitle()) + .content(placeReview.getContent()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request, User user, Place place) { + return PlaceReview.builder() + .user(user) + .place(place) + .title(request.getTitle()) + .content(request.getContent()) + .view(0L) + .build(); + } + // PlaceReview -> PlaceReviewDTO 변환 public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { return PlaceReviewResponseDTO.PlaceReviewDTO.builder() .reviewId(placeReview.getId()) - .placeIds(placeReview.getPlaceList().stream().map(prp -> prp.getPlace().getId()).collect(Collectors.toList())) // 해령: placeId -> placeIds로 변경 + .placeId(placeReview.getPlace().getId()) .title(placeReview.getTitle()) .content(placeReview.getContent()) .view(placeReview.getView()) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java new file mode 100644 index 00000000..304c8abf --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java @@ -0,0 +1,19 @@ +package com.otakumap.domain.place_review.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +public class PlaceReviewRequestDTO { + @Getter + public static class ReviewCreateRequestDTO { + private Long userId; // 토큰 사용 전 임시로 사용 + private Long placeId; // 임시 + @NotBlank + @Size(max = 20) + private String title; + @NotBlank + private String content; + // TODO: 이미지, 장소 활용 + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 709e0d84..1fe51c0a 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -11,13 +11,24 @@ import java.util.List; public class PlaceReviewResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewCreateResponseDTO { + private Long reviewId; + private String title; + private String content; + LocalDateTime createdAt; + } + @Builder @Getter @NoArgsConstructor @AllArgsConstructor public static class PlaceReviewDTO { private Long reviewId; - private List placeIds; // 해령: ids로 수정 + private Long placeId; private String title; private String content; private Long view; diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 3c3e0d69..5d533438 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -2,7 +2,6 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; @@ -27,7 +26,7 @@ public class PlaceReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 50) + @Column(nullable = false, length = 20) private String title; @Column(nullable = false, length = 3000) @@ -40,16 +39,17 @@ public class PlaceReview extends BaseEntity { // @JoinColumn(name = "image_id", referencedColumnName = "id") // private Image image; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview", orphanRemoval = true) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview") private List images = new ArrayList<>(); - @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL, orphanRemoval = true) - private List placeList = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; @@ -57,6 +57,4 @@ public class PlaceReview extends BaseEntity { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; - - public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java index 0beeaf01..6d4acb92 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java @@ -14,8 +14,6 @@ import java.util.List; -import static com.otakumap.domain.mapping.QPlaceReviewPlace.placeReviewPlace; - @Repository @RequiredArgsConstructor public class PlaceReviewRepositoryImpl implements PlaceReviewRepositoryCustom { @@ -34,12 +32,10 @@ public List findAllReviewsByPlace(Long placeId, String sort) { OrderSpecifier[] orderBy = getOrderSpecifier(placeReview, sort); return queryFactory.selectFrom(placeReview) -// .join(placeReview.place, place) // PlaceReview에서 Place로 조인 - .join(placeReview.placeList, placeReviewPlace) // PlaceReview에서 PlaceReviewPlace로 조인 - .join(placeReviewPlace.place, place) // PlaceReviewPlace에서 Place로 조인 + .join(placeReview.place, place) // PlaceReview에서 Place로 조인 .join(place.placeAnimationList, placeAnimation) // Place에서 PlaceAnimation으로 조인 .join(placeAnimation.animation, animation) // PlaceAnimation에서 Animation으로 조인 - .where(place.id.eq(placeId)) + .where(placeReview.place.id.eq(placeId)) .orderBy(orderBy) .fetch(); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java index a0fc05f3..4657b61d 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -1,5 +1,10 @@ package com.otakumap.domain.place_review.service; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.user.entity.User; + public interface PlaceReviewCommandService { + PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java index eaf67f53..79cd5240 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -1,6 +1,16 @@ package com.otakumap.domain.place_review.service; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.UserHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -9,6 +19,22 @@ @RequiredArgsConstructor public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService { private final PlaceReviewRepository placeReviewRepository; + private final PlaceRepository placeRepository; + private final UserRepository userRepository; + + @Override + @Transactional + public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { + User user = userRepository.findById(request.getUserId()) + .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); + + Place place = placeRepository.findById(request.getPlaceId()) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + PlaceReview placeReview = PlaceReviewConverter.toPlaceReview(request, user, place); + + return placeReviewRepository.save(placeReview); + } @Override @Transactional diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java deleted file mode 100644 index ec4844e2..00000000 --- a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.otakumap.domain.place_review_place.repository; - -import com.otakumap.domain.mapping.PlaceReviewPlace; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface PlaceReviewPlaceRepository extends JpaRepository { -} diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index f084a679..b1fad52d 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -1,26 +1,18 @@ package com.otakumap.domain.reviews.controller; -import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; -import com.otakumap.domain.reviews.service.ReviewCommandService; import com.otakumap.domain.reviews.service.ReviewQueryService; -import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ValidReviewId; 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.responses.ApiResponses; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import org.springframework.http.MediaType; -import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -29,7 +21,6 @@ public class ReviewController { private final ReviewQueryService reviewQueryService; - private final ReviewCommandService reviewCommandService; @GetMapping("/reviews/top7") @Operation(summary = "조회수 Top7 여행 후기 목록 조회", description = "조회수 Top7 여행 후기 목록을 조회합니다.") @@ -75,13 +66,4 @@ public ApiResponse getReviewDetail(@PathVaria return ApiResponse.onSuccess(reviewQueryService.getReviewDetail(reviewId, type)); } - - @PostMapping(value = "/reviews", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) - @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다. 장소, 이벤트 후기 중 하나만 작성할 수 있으며, 최소 1개 이상의 루트 아이템이 필요합니다.") - public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) - @RequestPart("request") @Valid ReviewRequestDTO.CreateDTO request, - @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { - ReviewResponseDTO.CreatedReviewDTO createdReview = reviewCommandService.createReview(request, user, images); - return ApiResponse.onSuccess(createdReview); - } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 14a9e813..c2993827 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,23 +1,9 @@ package com.otakumap.domain.reviews.converter; -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.mapping.EventAnimation; -import com.otakumap.domain.mapping.EventReviewPlace; -import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.PlaceReviewPlace; -import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import com.otakumap.domain.user.entity.User; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; @@ -67,8 +53,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(placeReview.getId()) -// .id(placeReview.getPlace().getId()) - .id(placeReview.getPlaceList().get(0).getPlace().getId()) // 해령: 임시로 수정 + .id(placeReview.getPlace().getId()) .title(placeReview.getTitle()) .content(placeReview.getContent()) .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) @@ -113,77 +98,4 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .route(RouteConverter.toRouteDTO(eventReview.getRoute())) .build(); } - - public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewId, String title) { - return ReviewResponseDTO.CreatedReviewDTO.builder() - .reviewId(reviewId) - .title(title) - .createdAt(LocalDateTime.now()) - .build(); - } - - public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, List eventReviewPlaces, Route route) { - return EventReview.builder() - .title(request.getTitle()) - .content(request.getContent()) - .view(0L) - .user(user) - .placeList(eventReviewPlaces) - .route(route) - .rating(0F) - .build(); - } - - public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, List placeReviewPlaces, Route route) { - return PlaceReview.builder() - .title(request.getTitle()) - .content(request.getContent()) - .view(0L) - .user(user) - .placeList(placeReviewPlaces) - .route(route) - .build(); - } - - public static List toPlaceReviewPlaceList(List places, PlaceReview placeReview) { - return places.stream() - .map(place -> PlaceReviewPlace.builder() - .placeReview(placeReview) - .place(place) - .build()) - .collect(Collectors.toList()); - } - - public static List toEventReviewPlaceList(List places, EventReview eventReview) { - return places.stream() - .map(place -> EventReviewPlace.builder() - .eventReview(eventReview) - .place(place) - .build()) - .collect(Collectors.toList()); - } - - public static Place toPlace(ReviewRequestDTO.RouteDTO routeDTO) { - return Place.builder() - .name(routeDTO.getName()) - .lat(routeDTO.getLat()) - .lng(routeDTO.getLng()) - .detail(routeDTO.getDetail()) - .isFavorite(false) - .build(); - } - - public static PlaceAnimation toPlaceAnimation(Place place, Animation animation) { - return PlaceAnimation.builder() - .place(place) - .animation(animation) - .build(); - } - - public static EventAnimation toEventAnimation(Event event, Animation animation) { - return EventAnimation.builder() - .event(event) - .animation(animation) - .build(); - } -} \ No newline at end of file +} diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java deleted file mode 100644 index bb725425..00000000 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.otakumap.domain.reviews.dto; - -import com.otakumap.domain.reviews.enums.ReviewType; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Getter; - -import java.util.List; - -public class ReviewRequestDTO { - @Getter - public static class CreateDTO { - @NotBlank(message = "제목을 입력해주세요.") - private String title; - - @NotBlank(message = "내용을 입력해주세요.") - private String content; - - @NotNull(message = "후기 종류를 입력해주세요. (place/event)") - private ReviewType reviewType; - - @NotNull(message = "애니메이션 id를 입력해주세요.") - private Long animeId; - - @Size(min = 1, message = "루트 아이템은 최소 1개 이상 필요합니다.") - private List routeItems; - } - - @Getter - public static class RouteDTO { - @NotBlank(message = "장소 이름을 입력해주세요.") - private String name; - - @NotNull - private double lat; - - @NotNull - private double lng; - - @NotBlank(message = "장소 설명을 입력해주세요.") - private String detail; - - @NotNull(message = "order를 입력해주세요.") - private Integer order; - } -} diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 634216c0..dbb4e9c7 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -66,14 +66,4 @@ public static class ReviewDetailDTO { RouteResponseDTO.RouteDTO route; } - - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class CreatedReviewDTO { - Long reviewId; - String title; - LocalDateTime createdAt; - } } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 00c0660a..32ca16b4 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -56,7 +56,7 @@ public Page getReviewsByKeyword(Stri .fetch(); List placeReviews = queryFactory.selectFrom(placeReview) -// .leftJoin(placeReview.place, QPlace.place) // 해령: placeReview와 place가 N:M 관계가 되어 주석 처리 + .leftJoin(placeReview.place, QPlace.place) .leftJoin(QPlace.place.placeAnimationList, placeAnimation) .leftJoin(placeAnimation.animation, animation) .where(placeCondition) diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java deleted file mode 100644 index 5233d9d9..00000000 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.otakumap.domain.reviews.service; - -import com.otakumap.domain.reviews.dto.ReviewRequestDTO; -import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import com.otakumap.domain.user.entity.User; -import org.springframework.web.multipart.MultipartFile; - -public interface ReviewCommandService { - ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images); -} diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java deleted file mode 100644 index 78a6cbef..00000000 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.otakumap.domain.reviews.service; - -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.animation.repository.AnimationRepository; -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.event_animation.repository.EventAnimationRepository; -import com.otakumap.domain.event_location.entity.EventLocation; -import com.otakumap.domain.event_location.repository.EventLocationRepository; -import com.otakumap.domain.event_review.entity.EventReview; -import com.otakumap.domain.event_review.repository.EventReviewRepository; -import com.otakumap.domain.event_review_place.repository.EventReviewPlaceRepository; -import com.otakumap.domain.image.service.ImageCommandService; -import com.otakumap.domain.mapping.EventReviewPlace; -import com.otakumap.domain.mapping.PlaceReviewPlace; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; -import com.otakumap.domain.reviews.converter.ReviewConverter; -import com.otakumap.domain.reviews.dto.ReviewRequestDTO; -import com.otakumap.domain.reviews.dto.ReviewResponseDTO; -import com.otakumap.domain.reviews.enums.ReviewType; -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route.repository.RouteRepository; -import com.otakumap.domain.route_item.converter.RouteItemConverter; -import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.repository.RouteItemRepository; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; -import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; -import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class ReviewCommandServiceImpl implements ReviewCommandService { - - private final PlaceReviewRepository placeReviewRepository; - private final EventReviewRepository eventReviewRepository; - private final AnimationRepository animationRepository; - private final PlaceRepository placeRepository; - private final RouteRepository routeRepository; - private final RouteItemRepository routeItemRepository; - private final ImageCommandService imageCommandService; - private final EventLocationRepository eventLocationRepository; - private final PlaceAnimationRepository placeAnimationRepository; - private final EventAnimationRepository eventAnimationRepository; - private final PlaceReviewPlaceRepository placeReviewPlaceRepository; - private final EventReviewPlaceRepository eventReviewPlaceRepository; - - @Override - @Transactional - public ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images) { - Animation animation = animationRepository.findById(request.getAnimeId()) - .orElseThrow(() -> new AnimationHandler(ErrorStatus.ANIMATION_NOT_FOUND)); - - Route route = saveRoute(request.getTitle()); - List routeItems = createRouteItems(request.getRouteItems(), animation, route); - routeItemRepository.saveAll(routeItems); - - return saveReview(request, user, images, route); - } - - // request의 장소 목록을 route item 객체로 변환 - private List createRouteItems(List routeDTOs, Animation animation, Route route) { - return routeDTOs.stream() - .map(routeDTO -> { - Place place = findOrSavePlace(routeDTO); - RouteItem routeItem = RouteItemConverter.toRouteItem(routeDTO.getOrder(), place); - routeItem.setRoute(route); - associateAnimationWithPlaceOrEvent(place, animation, getItemType(place)); - return routeItem; - }) - .collect(Collectors.toList()); - } - - private Place findOrSavePlace(ReviewRequestDTO.RouteDTO routeDTO) { - return placeRepository.findByNameAndLatAndLng(routeDTO.getName(), routeDTO.getLat(), routeDTO.getLng()) - .orElseGet(() -> placeRepository.save(ReviewConverter.toPlace(routeDTO))); - } - - // 장소인지 이벤트인지 확인 - private ReviewType getItemType(Place place) { - return eventLocationRepository.existsByLatitudeAndLongitude(place.getLat().toString(), place.getLng().toString()) ? ReviewType.EVENT : ReviewType.PLACE; - } - - // 장소 또는 이벤트에 애니메이션을 연결 - private void associateAnimationWithPlaceOrEvent(Place place, Animation animation, ReviewType itemType) { - if (itemType == ReviewType.PLACE) { - placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation)); - } else { - // Place에 해당하는 Event 찾기 - EventLocation eventLocation = eventLocationRepository.findByLatitudeAndLongitude(place.getLat().toString(), place.getLng().toString()) - .orElseThrow(() -> new ReviewHandler(ErrorStatus.EVENT_NOT_FOUND)); - Event event = eventLocation.getEvent(); - - // Event 객체를 이용해 EventAnimation 생성 및 저장 - eventAnimationRepository.save(ReviewConverter.toEventAnimation(event, animation)); - - } - } - - private Route saveRoute(String title) { - return routeRepository.save(Route.builder() - .name(title) - .routeItems(new ArrayList<>()) - .build()); - } - - // 리뷰 저장 및 반환 - private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images, Route route) { - List routeItems = routeItemRepository.findByRouteId(route.getId()); - - List places = routeItems.stream() - .map(routeItem -> placeRepository.findById(routeItem.getPlace().getId()) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND))) - .collect(Collectors.toList()); - - if (request.getReviewType() == ReviewType.PLACE) { - // 먼저 PlaceReview를 저장 - PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); - placeReview = placeReviewRepository.save(placeReview); - - // 저장된 PlaceReview를 기반으로 placeReviewPlaces 생성 - List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); - placeReviewPlaceRepository.saveAll(placeReviewPlaces); - - // placeList 업데이트 후 다시 저장 - placeReview.setPlaceList(placeReviewPlaces); - placeReview = placeReviewRepository.save(placeReview); - - imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ReviewType.PLACE); - return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); - - } else if (request.getReviewType() == ReviewType.EVENT) { - // 먼저 EventReview를 저장 - EventReview eventReview = ReviewConverter.toEventReview(request, user, new ArrayList<>(), route); - eventReview = eventReviewRepository.save(eventReview); - - // 저장된 EventReview를 기반으로 eventReviewPlaces 생성 - List eventReviewPlaces = ReviewConverter.toEventReviewPlaceList(places, eventReview); - eventReviewPlaceRepository.saveAll(eventReviewPlaces); - - // placeList 업데이트 후 다시 저장 - eventReview.setPlaceList(eventReviewPlaces); - eventReview = eventReviewRepository.save(eventReview); - - imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ReviewType.EVENT); - return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); - - } else { - throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index c3a5fbed..fb33ab1e 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -8,16 +8,6 @@ import java.util.List; public class RouteConverter { - - public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { - - return RouteResponseDTO.RouteDTO.builder() - .routeId(route.getId()) - .routeItems(route.getRouteItems().stream() - .map(RouteItemConverter::toRouteItemDTO).toList()) - .build(); - } - public static Route toRoute(String name, List routeItems) { Route route = Route.builder() .name(name) @@ -30,4 +20,13 @@ public static Route toRoute(String name, List routeItems) { return route; } + + + public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { + return RouteResponseDTO.RouteDTO.builder() + .routeId(route.getId()) + .routeItems(route.getRouteItems().stream() + .map(RouteItemConverter::toRouteItemDTO).toList()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 7984235c..92b7a129 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -5,16 +5,12 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.DynamicInsert; -import org.hibernate.annotations.DynamicUpdate; @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -@DynamicInsert -@DynamicUpdate public class RouteItem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java index 93d1ee52..63af4314 100644 --- a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -16,6 +16,4 @@ public interface RouteItemRepository extends JpaRepository { @Query("SELECT ri.place FROM RouteItem ri WHERE ri.route.id = :routeId") List findPlacesByRouteId(@Param("routeId") Long routeId); - - List findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index dfac5d16..896c0f7d 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -64,10 +64,6 @@ public enum ErrorStatus implements BaseErrorCode { // 애니메이션 관련 에러 ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4001", "존재하지 않는 애니메이션입니다"), PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4002", "존재하지 않는 애니메이션입니다"), - ANIMATION_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ANIMATION4003", "이미 존재하는 애니메이션입니다."), - ANIMATION_NAME_IS_EMPTY(HttpStatus.BAD_REQUEST, "ANIMATION4004", "애니메이션 이름이 비어있습니다."), - ANIMATION_NAME_LENGTH(HttpStatus.BAD_REQUEST, "ANIMATION4005", "애니메이션 이름은 2자 이상 50자 이하여야 합니다."), - ANIMATION_NAME_SPECIAL_CHARACTER(HttpStatus.BAD_REQUEST, "ANIMATION4006", "애니메이션 이름은 한글, 영문, 숫자 및 일부 특수문자(./-)만 포함할 수 있습니다."), // 루트 관련 에러 ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), @@ -91,13 +87,9 @@ public enum ErrorStatus implements BaseErrorCode { // 여행 후기 관련 에러 INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), - REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4003", "존재하지 않는 후기입니다."), // 이미지 관련 에러 - INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), - - // 검색 관련 에러 - INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."); + INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java deleted file mode 100644 index 25c8fff1..00000000 --- a/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.otakumap.global.apiPayload.exception.handler; - -import com.otakumap.global.apiPayload.code.BaseErrorCode; -import com.otakumap.global.apiPayload.exception.GeneralException; - -public class AnimationHandler extends GeneralException { - public AnimationHandler(BaseErrorCode errorCode) { super(errorCode); } -} \ No newline at end of file From 80780731710000c2b5cf96507eed3993a9fdd8a7 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:02:26 +0900 Subject: [PATCH 353/516] =?UTF-8?q?Refactor:=20isLiked=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/converter/AnimationConverter.java | 4 ++-- .../domain/animation/dto/AnimationResponseDTO.java | 2 +- .../otakumap/domain/event/converter/EventConverter.java | 8 ++------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java index aa397184..9931d7a7 100644 --- a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -8,12 +8,12 @@ public class AnimationConverter { - public static AnimationResponseDTO.AnimationInfoDTO toAnimationInfoDTO(PlaceAnimation placeAnimation, Boolean isFavorite, + public static AnimationResponseDTO.AnimationInfoDTO toAnimationInfoDTO(PlaceAnimation placeAnimation, Boolean isLiked, List hashTags) { return AnimationResponseDTO.AnimationInfoDTO.builder() .animationId(placeAnimation.getAnimation().getId()) .animationName(placeAnimation.getAnimation().getName()) - .isFavorite(isFavorite) + .isLiked(isLiked) .hashTags(hashTags) .build(); } diff --git a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java index 5c88f6c9..8466f899 100644 --- a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java @@ -16,7 +16,7 @@ public class AnimationResponseDTO { public static class AnimationInfoDTO { private Long animationId; private String animationName; - private Boolean isFavorite; + private Boolean isLiked; private List hashTags; } } diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index eff7bb6f..72c32b0d 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -7,10 +7,6 @@ import com.otakumap.domain.image.converter.ImageConverter; import org.springframework.data.domain.Page; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - import java.util.List; public class EventConverter { @@ -51,11 +47,11 @@ public static EventResponseDTO.EventSearchResultDTO toEventSearchResultDTO(Page< .build(); } - public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event event, Boolean isFavorite, String animationTitle, List hashTags) { + public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event event, Boolean isLiked, String animationTitle, List hashTags) { return EventResponseDTO.SearchedEventInfoDTO.builder() .eventId(event.getId()) .name(event.getName()) - .isLiked(isFavorite) + .isLiked(isLiked) .animationTitle(animationTitle) .hashTags(hashTags) .build(); From de25c2da19d7fa5662ebea61c33a19d808ccc2ee Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:03:35 +0900 Subject: [PATCH 354/516] =?UTF-8?q?Feat:=20CurrentUser=20required=3Dfalse?= =?UTF-8?q?=20=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/auth/jwt/annotation/CurrentUser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java b/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java index 0c484502..8d55fc41 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/annotation/CurrentUser.java @@ -11,4 +11,5 @@ @Retention(RetentionPolicy.RUNTIME) @Parameter(hidden = true) public @interface CurrentUser { + boolean required() default true; } \ No newline at end of file From 5679f344b77c91bf26e40375d3258790f1081128 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:05:07 +0900 Subject: [PATCH 355/516] =?UTF-8?q?Feat:=20api/map/**=20url=20Get=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=B9=84=ED=9A=8C=EC=9B=90=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 3113aa5f..26cb0d7c 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -40,6 +40,7 @@ public class SecurityConfig { private final String[] allowGetUrl = { "/api/events/**", "/api/reviews/**", + "/api/map/**", }; @Bean From 57bece1914dde9563fa99a84fe1db198338115ca Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:08:04 +0900 Subject: [PATCH 356/516] =?UTF-8?q?Feat:=20@CurrentUser(required=3Dfalse)?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/search/controller/SearchController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/search/controller/SearchController.java b/src/main/java/com/otakumap/domain/search/controller/SearchController.java index cb461e5c..5eb2c0ad 100644 --- a/src/main/java/com/otakumap/domain/search/controller/SearchController.java +++ b/src/main/java/com/otakumap/domain/search/controller/SearchController.java @@ -32,7 +32,7 @@ public class SearchController { @Parameters({ @Parameter(name = "keyword", description = "검색 키워드입니다."), }) - public ApiResponse> getSearchedPlaceInfoList(@CurrentUser User user, @RequestParam String keyword) { + public ApiResponse> getSearchedPlaceInfoList(@CurrentUser(required=false) User user, @RequestParam String keyword) { return ApiResponse.onSuccess(searchService.getSearchedResult(user, keyword)); } From c65158139033727f651095ac998beab8d70447c0 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:08:59 +0900 Subject: [PATCH 357/516] =?UTF-8?q?Feat:=20=EB=B9=84=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EC=9D=BC=20=EB=95=8C=20user=EC=97=90=20null=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/jwt/resolver/CurrentUserResolver.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java b/src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java index dce0c0b5..8de4663f 100644 --- a/src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java +++ b/src/main/java/com/otakumap/domain/auth/jwt/resolver/CurrentUserResolver.java @@ -30,8 +30,14 @@ public boolean supportsParameter(MethodParameter parameter) { public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { - PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); + Object principal = authentication.getPrincipal(); + // principal이 PrincipalDetails 타입이 아니면 (문자열 "anonymousUser"인 경우) null 반환 + if (!(principal instanceof PrincipalDetails)) { + return null; + } + PrincipalDetails principalDetails = (PrincipalDetails) principal; return userQueryService.getUserByEmail(principalDetails.getUsername()); } return null; From 0f25bdb5cf48649f98b1a6cc70a858400c6511e7 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:10:00 +0900 Subject: [PATCH 358/516] =?UTF-8?q?Feat:=20=EB=B9=84=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EC=9D=BC=20=EB=95=8C=20isLiked=EC=97=90=20false=20=EB=8C=80?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/search/service/SearchService.java | 1 - .../domain/search/service/SearchServiceImpl.java | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/search/service/SearchService.java b/src/main/java/com/otakumap/domain/search/service/SearchService.java index 8bd08c69..5c834823 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchService.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchService.java @@ -6,6 +6,5 @@ import java.util.List; public interface SearchService { - List getSearchedResult (User user, String keyword); } diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index 3bb99ddc..08759109 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -23,7 +23,9 @@ import com.otakumap.domain.search.dto.SearchResponseDTO; import com.otakumap.domain.search.repository.SearchRepositoryCustom; import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,6 +33,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +@Slf4j @Service @RequiredArgsConstructor public class SearchServiceImpl implements SearchService { @@ -41,6 +44,7 @@ public class SearchServiceImpl implements SearchService { private final EventHashTagRepository eventHashTagRepository; private final PlaceLikeRepository placeLikeRepository; private final PlaceRepository placeRepository; + private final UserRepository userRepository; @Transactional(readOnly = true) @Override @@ -106,8 +110,11 @@ public List getSearchedResult (User user, Str // 해당 위치의 이벤트들을 DTO로 변환 List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) .stream().map(event -> { - EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); - boolean isLiked = (eventLike != null); + boolean isLiked = false; + if(user != null) { + EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); + isLiked = (eventLike != null); + } // 이벤트에 연결된 해시태그 조회 List eventHashTags = eventHashTagRepository.findByEvent(event); @@ -130,8 +137,11 @@ public List getSearchedResult (User user, Str // 각 장소의 모든 PlaceAnimation을 AnimationInfoDTO 리스트로 변환 (애니메이션별 좋아요 여부 계산) List animationDTOs = place.getPlaceAnimationList().stream() .map(placeAnimation -> { + boolean isLiked = false; // 각 장소의 애니메이션별 좋아요 여부 - boolean isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); + if (user != null) { + isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); + } List hashTags = placeAnimation.getPlaceAnimationHashTags().stream() .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())) From 5b889919a196941fe16b8ad8ad5ee540f0216f07 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:12:37 +0900 Subject: [PATCH 359/516] =?UTF-8?q?Refactor:=20=EC=95=88=20=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20annotation=20=EB=B0=8F=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/search/service/SearchServiceImpl.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index 08759109..22108c2a 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -23,9 +23,7 @@ import com.otakumap.domain.search.dto.SearchResponseDTO; import com.otakumap.domain.search.repository.SearchRepositoryCustom; import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +31,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -@Slf4j @Service @RequiredArgsConstructor public class SearchServiceImpl implements SearchService { @@ -44,7 +41,6 @@ public class SearchServiceImpl implements SearchService { private final EventHashTagRepository eventHashTagRepository; private final PlaceLikeRepository placeLikeRepository; private final PlaceRepository placeRepository; - private final UserRepository userRepository; @Transactional(readOnly = true) @Override From 82598084aa40fd67928d639f188d561af8de0faa Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 12 Feb 2025 14:23:24 +0900 Subject: [PATCH 360/516] =?UTF-8?q?Fix:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20Ev?= =?UTF-8?q?entLocation=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java # src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java --- .../repository/EventLocationRepository.java | 3 +- .../service/ReviewCommandServiceImpl.java | 167 ++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java index 93ae5ca8..7536aeb3 100644 --- a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java +++ b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java @@ -6,5 +6,6 @@ import java.util.List; public interface EventLocationRepository extends JpaRepository { + boolean existsByLatAndLng(Double latitude, Double longitude); List findByLatAndLng(Double latitude, Double longitude); -} +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java new file mode 100644 index 00000000..dba302e0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -0,0 +1,167 @@ +package com.otakumap.domain.reviews.service; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.repository.AnimationRepository; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event_animation.repository.EventAnimationRepository; +import com.otakumap.domain.event_location.entity.EventLocation; +import com.otakumap.domain.event_location.repository.EventLocationRepository; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.domain.event_review_place.repository.EventReviewPlaceRepository; +import com.otakumap.domain.image.service.ImageCommandService; +import com.otakumap.domain.mapping.EventReviewPlace; +import com.otakumap.domain.mapping.PlaceReviewPlace; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; +import com.otakumap.domain.reviews.converter.ReviewConverter; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_item.converter.RouteItemConverter; +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.repository.RouteItemRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ReviewCommandServiceImpl implements ReviewCommandService { + + private final PlaceReviewRepository placeReviewRepository; + private final EventReviewRepository eventReviewRepository; + private final AnimationRepository animationRepository; + private final PlaceRepository placeRepository; + private final RouteRepository routeRepository; + private final RouteItemRepository routeItemRepository; + private final ImageCommandService imageCommandService; + private final EventLocationRepository eventLocationRepository; + private final PlaceAnimationRepository placeAnimationRepository; + private final EventAnimationRepository eventAnimationRepository; + private final PlaceReviewPlaceRepository placeReviewPlaceRepository; + private final EventReviewPlaceRepository eventReviewPlaceRepository; + + @Override + @Transactional + public ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images) { + Animation animation = animationRepository.findById(request.getAnimeId()) + .orElseThrow(() -> new AnimationHandler(ErrorStatus.ANIMATION_NOT_FOUND)); + + Route route = saveRoute(request.getTitle()); + List routeItems = createRouteItems(request.getRouteItems(), animation, route); + routeItemRepository.saveAll(routeItems); + + return saveReview(request, user, images, route); + } + + // request의 장소 목록을 route item 객체로 변환 + private List createRouteItems(List routeDTOs, Animation animation, Route route) { + return routeDTOs.stream() + .map(routeDTO -> { + Place place = findOrSavePlace(routeDTO); + RouteItem routeItem = RouteItemConverter.toRouteItem(routeDTO.getOrder(), place); + routeItem.setRoute(route); + associateAnimationWithPlaceOrEvent(place, animation, getItemType(place)); + return routeItem; + }) + .collect(Collectors.toList()); + } + + private Place findOrSavePlace(ReviewRequestDTO.RouteDTO routeDTO) { + return placeRepository.findByNameAndLatAndLng(routeDTO.getName(), routeDTO.getLat(), routeDTO.getLng()) + .orElseGet(() -> placeRepository.save(ReviewConverter.toPlace(routeDTO))); + } + + // 장소인지 이벤트인지 확인 + private ReviewType getItemType(Place place) { + return eventLocationRepository.existsByLatAndLng(place.getLat(), place.getLng()) ? ReviewType.EVENT : ReviewType.PLACE; + } + + // 장소 또는 이벤트에 애니메이션을 연결 + private void associateAnimationWithPlaceOrEvent(Place place, Animation animation, ReviewType itemType) { + if (itemType == ReviewType.PLACE) { + placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation)); + } else { + // Place에 해당하는 Event 찾기 + List eventLocations = eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()); + EventLocation eventLocation = eventLocations.stream().findFirst() + .orElseThrow(() -> new ReviewHandler(ErrorStatus.EVENT_NOT_FOUND)); + Event event = eventLocation.getEvent(); + + // Event 객체를 이용해 EventAnimation 생성 및 저장 + eventAnimationRepository.save(ReviewConverter.toEventAnimation(event, animation)); + + } + } + + private Route saveRoute(String title) { + return routeRepository.save(Route.builder() + .name(title) + .routeItems(new ArrayList<>()) + .build()); + } + + // 리뷰 저장 및 반환 + private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images, Route route) { + List routeItems = routeItemRepository.findByRouteId(route.getId()); + + List places = routeItems.stream() + .map(routeItem -> placeRepository.findById(routeItem.getPlace().getId()) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND))) + .collect(Collectors.toList()); + + if (request.getReviewType() == ReviewType.PLACE) { + // 먼저 PlaceReview를 저장 + PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); + placeReview = placeReviewRepository.save(placeReview); + + // 저장된 PlaceReview를 기반으로 placeReviewPlaces 생성 + List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); + placeReviewPlaceRepository.saveAll(placeReviewPlaces); + + // placeList 업데이트 후 다시 저장 + placeReview.setPlaceList(placeReviewPlaces); + placeReview = placeReviewRepository.save(placeReview); + + imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ReviewType.PLACE); + return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); + + } else if (request.getReviewType() == ReviewType.EVENT) { + // 먼저 EventReview를 저장 + EventReview eventReview = ReviewConverter.toEventReview(request, user, new ArrayList<>(), route); + eventReview = eventReviewRepository.save(eventReview); + + // 저장된 EventReview를 기반으로 eventReviewPlaces 생성 + List eventReviewPlaces = ReviewConverter.toEventReviewPlaceList(places, eventReview); + eventReviewPlaceRepository.saveAll(eventReviewPlaces); + + // placeList 업데이트 후 다시 저장 + eventReview.setPlaceList(eventReviewPlaces); + eventReview = eventReviewRepository.save(eventReview); + + imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ReviewType.EVENT); + return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); + + } else { + throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); + } + } + +} \ No newline at end of file From 8a331541bb8ca95202b7f7d390acf3b024120513 Mon Sep 17 00:00:00 2001 From: haerr <144093044+haerxeong@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:07:05 +0900 Subject: [PATCH 361/516] =?UTF-8?q?Fix:=20=EB=90=98=EB=8F=8C=EB=A6=AC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AnimationController.java | 51 ++++++++++ .../converter/AnimationConverter.java | 33 +++++++ .../animation/dto/AnimationRequestDTO.java | 12 +++ .../animation/dto/AnimationResponseDTO.java | 29 ++++++ .../domain/animation/entity/Animation.java | 2 +- .../repository/AnimationRepository.java | 6 ++ .../service/AnimationCommandService.java | 7 ++ .../service/AnimationCommandServiceImpl.java | 43 +++++++++ .../service/AnimationQueryService.java | 5 + .../service/AnimationQueryServiceImpl.java | 10 ++ .../repository/EventAnimationRepository.java | 7 ++ .../event_review/entity/EventReview.java | 8 +- .../EventReviewPlaceRepository.java | 7 ++ .../otakumap/domain/image/entity/Image.java | 4 + .../image/service/ImageCommandService.java | 3 +- .../service/ImageCommandServiceImpl.java | 24 ++++- .../domain/mapping/EventReviewPlace.java | 28 ++++++ .../domain/mapping/PlaceReviewPlace.java | 30 ++++++ .../otakumap/domain/place/entity/Place.java | 10 +- .../place/repository/PlaceRepository.java | 5 +- .../repository/PlaceAnimationRepository.java | 2 +- .../controller/PlaceReviewController.java | 13 --- .../converter/PlaceReviewConverter.java | 24 +---- .../dto/PlaceReviewRequestDTO.java | 19 ---- .../dto/PlaceReviewResponseDTO.java | 13 +-- .../place_review/entity/PlaceReview.java | 14 +-- .../repository/PlaceReviewRepositoryImpl.java | 8 +- .../service/PlaceReviewCommandService.java | 5 - .../PlaceReviewCommandServiceImpl.java | 26 ------ .../PlaceReviewPlaceRepository.java | 7 ++ .../reviews/controller/ReviewController.java | 18 ++++ .../reviews/converter/ReviewConverter.java | 92 ++++++++++++++++++- .../domain/reviews/dto/ReviewRequestDTO.java | 47 ++++++++++ .../domain/reviews/dto/ReviewResponseDTO.java | 10 ++ .../repository/ReviewRepositoryImpl.java | 2 +- .../reviews/service/ReviewCommandService.java | 10 ++ .../route/converter/RouteConverter.java | 19 ++-- .../domain/route_item/entity/RouteItem.java | 4 + .../repository/RouteItemRepository.java | 2 + .../apiPayload/code/status/ErrorStatus.java | 10 +- .../exception/handler/AnimationHandler.java | 8 ++ 41 files changed, 551 insertions(+), 126 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/animation/controller/AnimationController.java create mode 100644 src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java create mode 100644 src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java create mode 100644 src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java create mode 100644 src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java delete mode 100644 src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java create mode 100644 src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java diff --git a/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java new file mode 100644 index 00000000..4e1e40a5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/controller/AnimationController.java @@ -0,0 +1,51 @@ +package com.otakumap.domain.animation.controller; + +import com.otakumap.domain.animation.dto.AnimationRequestDTO; +import com.otakumap.domain.animation.dto.AnimationResponseDTO; +import com.otakumap.domain.animation.converter.AnimationConverter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.service.AnimationCommandService; +import com.otakumap.domain.animation.service.AnimationQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.SearchHandler; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/animations") +public class AnimationController { + private final AnimationQueryService animationQueryService; + private final AnimationCommandService animationCommandService; + + @GetMapping("/search") + @Operation(summary = "애니메이션 검색", description = "키워드로 애니메이션 제목을 검색해서 조회합니다. 공백은 허용되지 않습니다.") + public ApiResponse searchAnimation( + @RequestParam @NotBlank(message = "검색어를 입력해주세요") + @Pattern(regexp = "^[^\\s].*$", message = "첫 글자는 공백이 될 수 없습니다") String keyword) { + List animationList = animationQueryService.searchAnimation(keyword); + + if (animationList.isEmpty()) { + throw new SearchHandler(ErrorStatus.ANIMATION_NOT_FOUND); + } + + return ApiResponse.onSuccess(AnimationConverter.animationResultListDTO(animationList)); + } + + @PostMapping + @Operation(summary = "애니메이션 등록", description = "원하는 애니메이션이 없을 경우, 사용자가 애니메이션을 직접 등록합니다.") + public ApiResponse createAnimation( + @RequestBody @Valid AnimationRequestDTO.AnimationCreationRequestDTO request) { + Animation animation = animationCommandService.createAnimation(request.getName()); + return ApiResponse.onSuccess(AnimationConverter.toAnimationCreationResponseDTO(animation)); + } +} diff --git a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java index aa397184..2d7fc0e3 100644 --- a/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java +++ b/src/main/java/com/otakumap/domain/animation/converter/AnimationConverter.java @@ -1,6 +1,7 @@ package com.otakumap.domain.animation.converter; import com.otakumap.domain.animation.dto.AnimationResponseDTO; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.mapping.PlaceAnimation; @@ -17,4 +18,36 @@ public static AnimationResponseDTO.AnimationInfoDTO toAnimationInfoDTO(PlaceAnim .hashTags(hashTags) .build(); } + + public static AnimationResponseDTO.AnimationResultDTO animationResultDTO(Animation animation) { + return AnimationResponseDTO.AnimationResultDTO.builder() + .animationId(animation.getId()) + .name(animation.getName()) + .build(); + } + + public static AnimationResponseDTO.AnimationResultListDTO animationResultListDTO(List animations) { + List animationResultDTOs = animations.stream() + .map(AnimationConverter::animationResultDTO) + .toList(); + + return AnimationResponseDTO.AnimationResultListDTO.builder() + .animations(animationResultDTOs) + .listSize(animationResultDTOs.size()) + .build(); + } + + public static AnimationResponseDTO.AnimationCreationResponseDTO toAnimationCreationResponseDTO(Animation animation) { + return AnimationResponseDTO.AnimationCreationResponseDTO.builder() + .animationId(animation.getId()) + .name(animation.getName()) + .createdAt(animation.getCreatedAt()) + .build(); + } + + public static Animation toAnimation(String name) { + return Animation.builder() + .name(name) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java b/src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java new file mode 100644 index 00000000..28739413 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/dto/AnimationRequestDTO.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.animation.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +public class AnimationRequestDTO { + @Getter + public static class AnimationCreationRequestDTO { + @NotBlank + String name; + } +} diff --git a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java index 5c88f6c9..a7eb117d 100644 --- a/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java +++ b/src/main/java/com/otakumap/domain/animation/dto/AnimationResponseDTO.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; import java.util.List; public class AnimationResponseDTO { @@ -19,4 +20,32 @@ public static class AnimationInfoDTO { private Boolean isFavorite; private List hashTags; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationResultDTO { + Long animationId; + String name; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationResultListDTO { + List animations; + Integer listSize; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AnimationCreationResponseDTO { + Long animationId; + String name; + LocalDateTime createdAt; + } } diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index 1259b0c9..cd853f0e 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -15,6 +15,6 @@ public class Animation extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 50) private String name; } diff --git a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java index 6977cd99..f90e98f1 100644 --- a/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java +++ b/src/main/java/com/otakumap/domain/animation/repository/AnimationRepository.java @@ -2,6 +2,12 @@ import com.otakumap.domain.animation.entity.Animation; 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 AnimationRepository extends JpaRepository { + @Query("SELECT a FROM Animation a WHERE REPLACE(LOWER(a.name), ' ', '') LIKE CONCAT('%', REPLACE(LOWER(:keyword), ' ', ''), '%')") + List searchAnimationByKeyword(@Param("keyword") String keyword); } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java new file mode 100644 index 00000000..1ebbc25d --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.entity.Animation; + +public interface AnimationCommandService { + Animation createAnimation(String name); +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java new file mode 100644 index 00000000..bc6778a0 --- /dev/null +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java @@ -0,0 +1,43 @@ +package com.otakumap.domain.animation.service; + +import com.otakumap.domain.animation.converter.AnimationConverter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.animation.repository.AnimationRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AnimationCommandServiceImpl implements AnimationCommandService { + private final AnimationRepository animationRepository; + + @Override + @Transactional + public Animation createAnimation(String name) { + if (name == null || name.trim().isEmpty()) { + throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_IS_EMPTY); + } + + if (name.length() < 2 || name.length() > 50) { + throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_LENGTH); + } + + if (!name.matches("^[가-힣a-zA-Z0-9\\\\s./()-]+$")) { + throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_SPECIAL_CHARACTER); + } + + String normalizedName = name.replaceAll("\\s+", "").toLowerCase(); + + // 중복 검사 + if (animationRepository.findAll().stream() + .anyMatch(animation -> animation.getName().replaceAll("\\s+", "").toLowerCase().equals(normalizedName))) { + throw new AnimationHandler(ErrorStatus.ANIMATION_ALREADY_EXISTS); + } + + Animation newAnimation = AnimationConverter.toAnimation(name); + return animationRepository.save(newAnimation); + } +} diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java index b569c48e..6070b6f8 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryService.java @@ -1,5 +1,10 @@ package com.otakumap.domain.animation.service; +import com.otakumap.domain.animation.entity.Animation; + +import java.util.List; + public interface AnimationQueryService { + List searchAnimation(String keyword); boolean existsById(Long animationId); } diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java index b7896104..4bf5d88e 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationQueryServiceImpl.java @@ -1,9 +1,13 @@ package com.otakumap.domain.animation.service; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.repository.AnimationRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class AnimationQueryServiceImpl implements AnimationQueryService { @@ -13,4 +17,10 @@ public class AnimationQueryServiceImpl implements AnimationQueryService { public boolean existsById(Long animationId) { return animationRepository.existsById(animationId); } + + @Override + @Transactional + public List searchAnimation(String keyword) { + return animationRepository.searchAnimationByKeyword(keyword); + } } diff --git a/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java b/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java new file mode 100644 index 00000000..ea70b12c --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_animation/repository/EventAnimationRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event_animation.repository; + +import com.otakumap.domain.mapping.EventAnimation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventAnimationRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index d915b57f..c93da32d 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -3,6 +3,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -26,7 +27,7 @@ public class EventReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(length = 20, nullable = false) + @Column(length = 50, nullable = false) private String title; @Column(columnDefinition = "text not null") @@ -45,6 +46,9 @@ public class EventReview extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") private List images = new ArrayList<>(); + @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL) + private List placeList = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; @@ -60,4 +64,6 @@ public class EventReview extends BaseEntity { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; + + public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java new file mode 100644 index 00000000..ab386f0c --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_review_place/repository/EventReviewPlaceRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.event_review_place.repository; + +import com.otakumap.domain.mapping.EventReviewPlace; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventReviewPlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java index 91f5c9bf..c733d55a 100644 --- a/src/main/java/com/otakumap/domain/image/entity/Image.java +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -33,4 +33,8 @@ public class Image extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_review_id") private EventReview eventReview; + + public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } + + public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java index 9df0bb58..7ab1825f 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandService.java @@ -1,13 +1,14 @@ package com.otakumap.domain.image.service; import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.reviews.enums.ReviewType; import org.springframework.web.multipart.MultipartFile; import java.util.List; public interface ImageCommandService { Image uploadProfileImage(MultipartFile file, Long userId); - List uploadReviewImages(List files, Long reviewId); + List uploadReviewImages(List files, Long reviewId, ReviewType reviewType); Image uploadImage(MultipartFile file, String folder); } diff --git a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java index f335b4ea..71d6a63e 100644 --- a/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/image/service/ImageCommandServiceImpl.java @@ -1,10 +1,16 @@ package com.otakumap.domain.image.service; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.repository.ImageRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.ImageHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.util.AmazonS3Util; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -19,6 +25,8 @@ public class ImageCommandServiceImpl implements ImageCommandService { private final ImageRepository imageRepository; private final AmazonS3Util amazonS3Util; + private final EventReviewRepository eventReviewRepository; + private final PlaceReviewRepository placeReviewRepository; @Override @Transactional @@ -31,15 +39,25 @@ public Image uploadProfileImage(MultipartFile file, Long userId) { @Override @Transactional - public List uploadReviewImages(List files, Long reviewId) { + public List uploadReviewImages(List files, Long reviewId, ReviewType reviewType) { return files.stream() .map(file -> { String keyName = amazonS3Util.generateReviewKeyName(); String fileUrl = amazonS3Util.uploadFile(keyName, file); + Image image = ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); - return ImageConverter.toImage((keyName.split("/")[1]), keyName, fileUrl); + if (reviewType == ReviewType.EVENT) { + EventReview eventReview = eventReviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + image.setEventReview(eventReview); + } else if (reviewType == ReviewType.PLACE) { + PlaceReview placeReview = placeReviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + image.setPlaceReview(placeReview); + } + + return imageRepository.save(image); }) - .map(imageRepository::save) .collect(Collectors.toList()); } diff --git a/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java new file mode 100644 index 00000000..f4f7805a --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/EventReviewPlace.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class EventReviewPlace extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_review_id") + private EventReview eventReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; + + public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } +} diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java new file mode 100644 index 00000000..070d2238 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/PlaceReviewPlace.java @@ -0,0 +1,30 @@ +package com.otakumap.domain.mapping; + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PlaceReviewPlace extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_review_id") + private PlaceReview placeReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id") + private Place place; + + public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } + + public void setPlace(Place place) { this.place = place; } +} diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index bbb46eba..585b506c 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,6 +1,8 @@ package com.otakumap.domain.place.entity; +import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.route_item.entity.RouteItem; import com.otakumap.global.common.BaseEntity; @@ -23,7 +25,7 @@ public class Place extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 50) private String name; @Column(nullable = false) @@ -45,6 +47,12 @@ public class Place extends BaseEntity { @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List placeAnimationList = new ArrayList<>(); + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List placeReviewList = new ArrayList<>(); + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) + private List eventReviewList = new ArrayList<>(); + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL, orphanRemoval = true) private List routeItems = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 1c8e3924..6819f675 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -3,8 +3,11 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + import java.util.List; public interface PlaceRepository extends JpaRepository { List findByLatAndLng(Double lat, Double lng); -} + Optional findByNameAndLatAndLng(String name, Double lat, Double lng); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index 2875afb9..8837a058 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -10,4 +10,4 @@ public interface PlaceAnimationRepository extends JpaRepository findByIdAndPlaceId(Long id, Long placeId); List findByPlaceId(Long placeId); Optional findByPlaceIdAndAnimationId(Long placeId, Long animationId); -} \ No newline at end of file +} diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index f5df232f..8d0a3680 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -1,16 +1,11 @@ package com.otakumap.domain.place_review.controller; -import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.place_review.service.PlaceReviewCommandService; import com.otakumap.domain.place_review.service.PlaceReviewQueryService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -18,16 +13,8 @@ @RequiredArgsConstructor @RequestMapping("/api") public class PlaceReviewController { - private final PlaceReviewCommandService placeReviewCommandService; private final PlaceReviewQueryService placeReviewQueryService; - @PostMapping("/review") - @Operation(summary = "리뷰 작성") - public ApiResponse createReview(@RequestBody @Valid PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { - PlaceReview placeReview = placeReviewCommandService.createReview(request); - return ApiResponse.onSuccess(PlaceReviewConverter.toReviewCreateResponseDTO(placeReview)); - } - @GetMapping("/places/{placeId}/reviews") @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") @Parameters({ diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 48bf7a8d..517f408e 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -4,41 +4,23 @@ import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.entity.User; import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Collectors; public class PlaceReviewConverter { - public static PlaceReviewResponseDTO.ReviewCreateResponseDTO toReviewCreateResponseDTO(PlaceReview placeReview) { - return PlaceReviewResponseDTO.ReviewCreateResponseDTO.builder() - .reviewId(placeReview.getId()) - .title(placeReview.getTitle()) - .content(placeReview.getContent()) - .createdAt(LocalDateTime.now()) - .build(); - } - - public static PlaceReview toPlaceReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request, User user, Place place) { - return PlaceReview.builder() - .user(user) - .place(place) - .title(request.getTitle()) - .content(request.getContent()) - .view(0L) - .build(); - } - // PlaceReview -> PlaceReviewDTO 변환 public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview placeReview) { return PlaceReviewResponseDTO.PlaceReviewDTO.builder() .reviewId(placeReview.getId()) - .placeId(placeReview.getPlace().getId()) + .placeIds(placeReview.getPlaceList().stream().map(prp -> prp.getPlace().getId()).collect(Collectors.toList())) // 해령: placeId -> placeIds로 변경 .title(placeReview.getTitle()) .content(placeReview.getContent()) .view(placeReview.getView()) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java deleted file mode 100644 index 304c8abf..00000000 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewRequestDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.otakumap.domain.place_review.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import lombok.Getter; - -public class PlaceReviewRequestDTO { - @Getter - public static class ReviewCreateRequestDTO { - private Long userId; // 토큰 사용 전 임시로 사용 - private Long placeId; // 임시 - @NotBlank - @Size(max = 20) - private String title; - @NotBlank - private String content; - // TODO: 이미지, 장소 활용 - } -} diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 1fe51c0a..709e0d84 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -11,24 +11,13 @@ import java.util.List; public class PlaceReviewResponseDTO { - @Builder - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class ReviewCreateResponseDTO { - private Long reviewId; - private String title; - private String content; - LocalDateTime createdAt; - } - @Builder @Getter @NoArgsConstructor @AllArgsConstructor public static class PlaceReviewDTO { private Long reviewId; - private Long placeId; + private List placeIds; // 해령: ids로 수정 private String title; private String content; private Long view; diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 5d533438..3c3e0d69 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -2,6 +2,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; @@ -26,7 +27,7 @@ public class PlaceReview extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 50) private String title; @Column(nullable = false, length = 3000) @@ -39,17 +40,16 @@ public class PlaceReview extends BaseEntity { // @JoinColumn(name = "image_id", referencedColumnName = "id") // private Image image; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview") + @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview", orphanRemoval = true) private List images = new ArrayList<>(); + @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL, orphanRemoval = true) + private List placeList = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id", nullable = false) - private Place place; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; @@ -57,4 +57,6 @@ public class PlaceReview extends BaseEntity { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; + + public void setPlaceList(List placeList) { this.placeList = placeList; } } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java index 6d4acb92..0beeaf01 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepositoryImpl.java @@ -14,6 +14,8 @@ import java.util.List; +import static com.otakumap.domain.mapping.QPlaceReviewPlace.placeReviewPlace; + @Repository @RequiredArgsConstructor public class PlaceReviewRepositoryImpl implements PlaceReviewRepositoryCustom { @@ -32,10 +34,12 @@ public List findAllReviewsByPlace(Long placeId, String sort) { OrderSpecifier[] orderBy = getOrderSpecifier(placeReview, sort); return queryFactory.selectFrom(placeReview) - .join(placeReview.place, place) // PlaceReview에서 Place로 조인 +// .join(placeReview.place, place) // PlaceReview에서 Place로 조인 + .join(placeReview.placeList, placeReviewPlace) // PlaceReview에서 PlaceReviewPlace로 조인 + .join(placeReviewPlace.place, place) // PlaceReviewPlace에서 Place로 조인 .join(place.placeAnimationList, placeAnimation) // Place에서 PlaceAnimation으로 조인 .join(placeAnimation.animation, animation) // PlaceAnimation에서 Animation으로 조인 - .where(placeReview.place.id.eq(placeId)) + .where(place.id.eq(placeId)) .orderBy(orderBy) .fetch(); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java index 4657b61d..a0fc05f3 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandService.java @@ -1,10 +1,5 @@ package com.otakumap.domain.place_review.service; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.user.entity.User; - public interface PlaceReviewCommandService { - PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request); void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java index 79cd5240..eaf67f53 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewCommandServiceImpl.java @@ -1,16 +1,6 @@ package com.otakumap.domain.place_review.service; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; -import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -19,22 +9,6 @@ @RequiredArgsConstructor public class PlaceReviewCommandServiceImpl implements PlaceReviewCommandService { private final PlaceReviewRepository placeReviewRepository; - private final PlaceRepository placeRepository; - private final UserRepository userRepository; - - @Override - @Transactional - public PlaceReview createReview(PlaceReviewRequestDTO.ReviewCreateRequestDTO request) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND)); - - Place place = placeRepository.findById(request.getPlaceId()) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - - PlaceReview placeReview = PlaceReviewConverter.toPlaceReview(request, user, place); - - return placeReviewRepository.save(placeReview); - } @Override @Transactional diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java new file mode 100644 index 00000000..ec4844e2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.place_review_place.repository; + +import com.otakumap.domain.mapping.PlaceReviewPlace; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceReviewPlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index b1fad52d..f084a679 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -1,18 +1,26 @@ package com.otakumap.domain.reviews.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.reviews.service.ReviewCommandService; import com.otakumap.domain.reviews.service.ReviewQueryService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ValidReviewId; 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.responses.ApiResponses; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.http.MediaType; +import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -21,6 +29,7 @@ public class ReviewController { private final ReviewQueryService reviewQueryService; + private final ReviewCommandService reviewCommandService; @GetMapping("/reviews/top7") @Operation(summary = "조회수 Top7 여행 후기 목록 조회", description = "조회수 Top7 여행 후기 목록을 조회합니다.") @@ -66,4 +75,13 @@ public ApiResponse getReviewDetail(@PathVaria return ApiResponse.onSuccess(reviewQueryService.getReviewDetail(reviewId, type)); } + + @PostMapping(value = "/reviews", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다. 장소, 이벤트 후기 중 하나만 작성할 수 있으며, 최소 1개 이상의 루트 아이템이 필요합니다.") + public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) + @RequestPart("request") @Valid ReviewRequestDTO.CreateDTO request, + @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { + ReviewResponseDTO.CreatedReviewDTO createdReview = reviewCommandService.createReview(request, user, images); + return ApiResponse.onSuccess(createdReview); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index c2993827..14a9e813 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,9 +1,23 @@ package com.otakumap.domain.reviews.converter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.mapping.EventReviewPlace; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.user.entity.User; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; @@ -53,7 +67,8 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(placeReview.getId()) - .id(placeReview.getPlace().getId()) +// .id(placeReview.getPlace().getId()) + .id(placeReview.getPlaceList().get(0).getPlace().getId()) // 해령: 임시로 수정 .title(placeReview.getTitle()) .content(placeReview.getContent()) .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) @@ -98,4 +113,77 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .route(RouteConverter.toRouteDTO(eventReview.getRoute())) .build(); } -} + + public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewId, String title) { + return ReviewResponseDTO.CreatedReviewDTO.builder() + .reviewId(reviewId) + .title(title) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, List eventReviewPlaces, Route route) { + return EventReview.builder() + .title(request.getTitle()) + .content(request.getContent()) + .view(0L) + .user(user) + .placeList(eventReviewPlaces) + .route(route) + .rating(0F) + .build(); + } + + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, List placeReviewPlaces, Route route) { + return PlaceReview.builder() + .title(request.getTitle()) + .content(request.getContent()) + .view(0L) + .user(user) + .placeList(placeReviewPlaces) + .route(route) + .build(); + } + + public static List toPlaceReviewPlaceList(List places, PlaceReview placeReview) { + return places.stream() + .map(place -> PlaceReviewPlace.builder() + .placeReview(placeReview) + .place(place) + .build()) + .collect(Collectors.toList()); + } + + public static List toEventReviewPlaceList(List places, EventReview eventReview) { + return places.stream() + .map(place -> EventReviewPlace.builder() + .eventReview(eventReview) + .place(place) + .build()) + .collect(Collectors.toList()); + } + + public static Place toPlace(ReviewRequestDTO.RouteDTO routeDTO) { + return Place.builder() + .name(routeDTO.getName()) + .lat(routeDTO.getLat()) + .lng(routeDTO.getLng()) + .detail(routeDTO.getDetail()) + .isFavorite(false) + .build(); + } + + public static PlaceAnimation toPlaceAnimation(Place place, Animation animation) { + return PlaceAnimation.builder() + .place(place) + .animation(animation) + .build(); + } + + public static EventAnimation toEventAnimation(Event event, Animation animation) { + return EventAnimation.builder() + .event(event) + .animation(animation) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java new file mode 100644 index 00000000..bb725425 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -0,0 +1,47 @@ +package com.otakumap.domain.reviews.dto; + +import com.otakumap.domain.reviews.enums.ReviewType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +import java.util.List; + +public class ReviewRequestDTO { + @Getter + public static class CreateDTO { + @NotBlank(message = "제목을 입력해주세요.") + private String title; + + @NotBlank(message = "내용을 입력해주세요.") + private String content; + + @NotNull(message = "후기 종류를 입력해주세요. (place/event)") + private ReviewType reviewType; + + @NotNull(message = "애니메이션 id를 입력해주세요.") + private Long animeId; + + @Size(min = 1, message = "루트 아이템은 최소 1개 이상 필요합니다.") + private List routeItems; + } + + @Getter + public static class RouteDTO { + @NotBlank(message = "장소 이름을 입력해주세요.") + private String name; + + @NotNull + private double lat; + + @NotNull + private double lng; + + @NotBlank(message = "장소 설명을 입력해주세요.") + private String detail; + + @NotNull(message = "order를 입력해주세요.") + private Integer order; + } +} diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index dbb4e9c7..634216c0 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -66,4 +66,14 @@ public static class ReviewDetailDTO { RouteResponseDTO.RouteDTO route; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CreatedReviewDTO { + Long reviewId; + String title; + LocalDateTime createdAt; + } } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 32ca16b4..00c0660a 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -56,7 +56,7 @@ public Page getReviewsByKeyword(Stri .fetch(); List placeReviews = queryFactory.selectFrom(placeReview) - .leftJoin(placeReview.place, QPlace.place) +// .leftJoin(placeReview.place, QPlace.place) // 해령: placeReview와 place가 N:M 관계가 되어 주석 처리 .leftJoin(QPlace.place.placeAnimationList, placeAnimation) .leftJoin(placeAnimation.animation, animation) .where(placeCondition) diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java new file mode 100644 index 00000000..5233d9d9 --- /dev/null +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.reviews.service; + +import com.otakumap.domain.reviews.dto.ReviewRequestDTO; +import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.user.entity.User; +import org.springframework.web.multipart.MultipartFile; + +public interface ReviewCommandService { + ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images); +} diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index fb33ab1e..c3a5fbed 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -8,6 +8,16 @@ import java.util.List; public class RouteConverter { + + public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { + + return RouteResponseDTO.RouteDTO.builder() + .routeId(route.getId()) + .routeItems(route.getRouteItems().stream() + .map(RouteItemConverter::toRouteItemDTO).toList()) + .build(); + } + public static Route toRoute(String name, List routeItems) { Route route = Route.builder() .name(name) @@ -20,13 +30,4 @@ public static Route toRoute(String name, List routeItems) { return route; } - - - public static RouteResponseDTO.RouteDTO toRouteDTO(Route route) { - return RouteResponseDTO.RouteDTO.builder() - .routeId(route.getId()) - .routeItems(route.getRouteItems().stream() - .map(RouteItemConverter::toRouteItemDTO).toList()) - .build(); - } } diff --git a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java index 92b7a129..7984235c 100644 --- a/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java +++ b/src/main/java/com/otakumap/domain/route_item/entity/RouteItem.java @@ -5,12 +5,16 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor +@DynamicInsert +@DynamicUpdate public class RouteItem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java index 63af4314..93d1ee52 100644 --- a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -16,4 +16,6 @@ public interface RouteItemRepository extends JpaRepository { @Query("SELECT ri.place FROM RouteItem ri WHERE ri.route.id = :routeId") List findPlacesByRouteId(@Param("routeId") Long routeId); + + List findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 896c0f7d..dfac5d16 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -64,6 +64,10 @@ public enum ErrorStatus implements BaseErrorCode { // 애니메이션 관련 에러 ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4001", "존재하지 않는 애니메이션입니다"), PLACE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ANIMATION4002", "존재하지 않는 애니메이션입니다"), + ANIMATION_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ANIMATION4003", "이미 존재하는 애니메이션입니다."), + ANIMATION_NAME_IS_EMPTY(HttpStatus.BAD_REQUEST, "ANIMATION4004", "애니메이션 이름이 비어있습니다."), + ANIMATION_NAME_LENGTH(HttpStatus.BAD_REQUEST, "ANIMATION4005", "애니메이션 이름은 2자 이상 50자 이하여야 합니다."), + ANIMATION_NAME_SPECIAL_CHARACTER(HttpStatus.BAD_REQUEST, "ANIMATION4006", "애니메이션 이름은 한글, 영문, 숫자 및 일부 특수문자(./-)만 포함할 수 있습니다."), // 루트 관련 에러 ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), @@ -87,9 +91,13 @@ public enum ErrorStatus implements BaseErrorCode { // 여행 후기 관련 에러 INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), + REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4003", "존재하지 않는 후기입니다."), // 이미지 관련 에러 - INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."); + INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), + + // 검색 관련 에러 + INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java new file mode 100644 index 00000000..25c8fff1 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/AnimationHandler.java @@ -0,0 +1,8 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class AnimationHandler extends GeneralException { + public AnimationHandler(BaseErrorCode errorCode) { super(errorCode); } +} \ No newline at end of file From 826ceeae3235b1c03876190b06f10612d8c68173 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 12 Feb 2025 15:43:49 +0900 Subject: [PATCH 362/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=EA=B0=80=20=EC=83=88=EB=A1=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EB=90=98=EC=A7=80=20=EC=95=8A=EA=B3=A0=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EA=B2=83=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/repository/RouteLikeRepository.java | 4 +--- .../route_like/service/RouteLikeCommandServiceImpl.java | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java index 666076f9..da9146c5 100644 --- a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java +++ b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java @@ -9,7 +9,5 @@ public interface RouteLikeRepository extends JpaRepository { boolean existsByUserAndRoute(User user, Route route); - - // route_id로 RouteLike 조회 - Optional findByRouteId(Long routeId); + Optional findByUserAndRoute(User user, Route route); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 654aa99c..0ddb2896 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -102,6 +102,7 @@ public RouteLike updateRouteLike(RouteLikeRequestDTO.UpdateRouteLikeDTO request, route.setName(request.getName()); route.setRouteItems(updatedRouteItems); - return routeLikeRepository.save(RouteLikeConverter.toRouteLike(user, route)); + + return routeLikeRepository.findByUserAndRoute(user, route).orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_LIKE_NOT_FOUND)); } } From 3563c396b423bd6728ecf16f3bb5fa3f22796642 Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 12 Feb 2025 15:48:59 +0900 Subject: [PATCH 363/516] =?UTF-8?q?Feat:=20isFavorite=20->=20isLiked=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EB=B0=8F=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/converter/PlaceLikeConverter.java | 2 +- .../otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index b422daa4..200d95fb 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -53,7 +53,7 @@ public static PlaceLikeResponseDTO.PlaceLikeDetailDTO placeLikeDetailDTO(PlaceLi .animationName(placeLike.getPlaceAnimation().getAnimation().getName()) .lat(place.getLat()) .lng(place.getLng()) - .isFavorite(placeLike.getIsFavorite()) + .isLiked(Boolean.TRUE) // 저장한 장소를 조회하는 거니까 항상 true // 장소-애니메이션에 대한 해시태그 .hashtags(placeLike.getPlaceAnimation().getPlaceAnimationHashTags() .stream() diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 254958da..0434c5e6 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -52,7 +52,7 @@ public static class PlaceLikeDetailDTO { String animationName; Double lat; Double lng; - Boolean isFavorite; + Boolean isLiked; List hashtags; } } \ No newline at end of file From 3d751cf87425379014bb3a1fb72e4273de972e2f Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 12 Feb 2025 16:32:32 +0900 Subject: [PATCH 364/516] =?UTF-8?q?Feat:=20HTTPS=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/CorsConfig.java | 3 ++- src/main/java/com/otakumap/global/config/WebConfig.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java index b32daad4..ce7f013d 100644 --- a/src/main/java/com/otakumap/global/config/CorsConfig.java +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -18,7 +18,8 @@ public static CorsConfigurationSource corsConfigurationSource() { configuration.setAllowedOriginPatterns(Arrays.asList( "http://localhost:3000", "https://otakumap.netlify.app", - "https://deploy-preview-*--otakumap.netlify.app" // 모든 프리뷰 URL 허용 + "https://deploy-preview-*--otakumap.netlify.app", // 모든 프리뷰 URL 허용 + "https://api.otakumap.site" )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java index 4b91a2ac..09116365 100644 --- a/src/main/java/com/otakumap/global/config/WebConfig.java +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -25,7 +25,7 @@ public void addArgumentResolvers(List resolvers) @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:3000") + .allowedOrigins("http://localhost:3000", "https://api.otakumap.site") .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) From 6df1447763558cc55a3b823ba1f08a80155dce3c Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 12 Feb 2025 17:37:51 +0900 Subject: [PATCH 365/516] =?UTF-8?q?Fix:=20CORS=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84=ED=95=B4=20WebConfig?= =?UTF-8?q?=EC=97=90=20=EB=B0=B0=ED=8F=AC=EB=90=9C=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20URL=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java index 09116365..658c44b5 100644 --- a/src/main/java/com/otakumap/global/config/WebConfig.java +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -25,7 +25,7 @@ public void addArgumentResolvers(List resolvers) @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:3000", "https://api.otakumap.site") + .allowedOrigins("http://localhost:3000", "https://otakumap.netlify.app", "https://deploy-preview-*--otakumap.netlify.app", "https://api.otakumap.site") .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) From f49a2fd7b6dcd97451dbfc1240838c0e1d095b35 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:31:32 +0900 Subject: [PATCH 366/516] =?UTF-8?q?Feat:=20isLiked=20=ED=8C=90=EB=B3=84=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20exists=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/repository/EventLikeRepository.java | 2 +- .../domain/place_like/repository/PlaceLikeRepository.java | 2 -- .../com/otakumap/domain/search/service/SearchServiceImpl.java | 4 +--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java index 46c8abdf..a07f21b1 100644 --- a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java +++ b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java @@ -9,5 +9,5 @@ public interface EventLikeRepository extends JpaRepository { - EventLike findByUserAndEvent(User user, Event event); + Boolean existsByUserAndEvent(User user, Event event); } diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index 559244cd..d5b42168 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -11,6 +11,4 @@ public interface PlaceLikeRepository extends JpaRepository { boolean existsByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); // 특정 Place와 연결된 PlaceLike가 존재하는지 확인 Optional findByPlaceIdAndUserId(Long placeId, Long userId); - - Optional findByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index 22108c2a..f1c58c7a 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -6,7 +6,6 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.entity.enums.EventStatus; -import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.event_location.entity.EventLocation; import com.otakumap.domain.event_location.repository.EventLocationRepository; @@ -108,8 +107,7 @@ public List getSearchedResult (User user, Str .stream().map(event -> { boolean isLiked = false; if(user != null) { - EventLike eventLike = eventLikeRepository.findByUserAndEvent(user, event); - isLiked = (eventLike != null); + isLiked = eventLikeRepository.existsByUserAndEvent(user, event); } // 이벤트에 연결된 해시태그 조회 From 41c851860bddd2909d18520ad337b7b0c6d54331 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:04:36 +0900 Subject: [PATCH 367/516] =?UTF-8?q?Feat:=20animationName=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20=EC=95=A0=EB=8B=88=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/converter/EventConverter.java | 4 ++-- src/main/java/com/otakumap/domain/event/entity/Event.java | 3 --- .../domain/event/service/EventQueryServiceImpl.java | 7 ++++++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index 72c32b0d..b3af9f6b 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -21,11 +21,11 @@ public static EventResponseDTO.EventDTO toEventDTO(Event event) { .build(); } - public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event) { + public static EventResponseDTO.EventDetailDTO toEventDetailDTO(Event event, String animationName) { return EventResponseDTO.EventDetailDTO.builder() .id(event.getId()) .title(event.getTitle()) - .animationName(event.getAnimationName()) + .animationName(animationName) .name(event.getName()) .site(event.getSite()) .startDate(event.getStartDate()) diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index d294e063..2dc9b4e7 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -34,9 +34,6 @@ public class Event extends BaseEntity { @Column(nullable = false, length = 50) // 이벤트 일본어 원제 private String name; - @Column(nullable = false, length = 50) // 이벤트에 해당하는 애니메이션 이름 - private String animationName; - @Column(nullable = false) private LocalDate startDate; diff --git a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java index d58970b2..705498b7 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java @@ -10,6 +10,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -20,7 +22,10 @@ public class EventQueryServiceImpl implements EventQueryService{ @Override public EventResponseDTO.EventDetailDTO getEventDetail(Long eventId) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + String animationName = event.getEventAnimationList().stream() + .map(ea -> ea.getAnimation().getName()) + .collect(Collectors.joining(", ")); - return EventConverter.toEventDetailDTO(event); + return EventConverter.toEventDetailDTO(event, animationName); } } From 035a58daa52419b2487078a1ec14d8ec76ba93b3 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 12 Feb 2025 22:20:25 +0900 Subject: [PATCH 368/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_review/converter/PlaceReviewConverter.java | 4 +--- .../com/otakumap/domain/place_review/entity/PlaceReview.java | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 48bf7a8d..8ad5a92b 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -1,8 +1,6 @@ package com.otakumap.domain.place_review.converter; import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.hash_tag.converter.HashTagConverter; -import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.dto.PlaceReviewRequestDTO; @@ -43,7 +41,7 @@ public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview .content(placeReview.getContent()) .view(placeReview.getView()) .createdAt(placeReview.getCreatedAt()) - .reviewImage(ImageConverter.toImageDTO(placeReview.getImages().get(0))) // 나중에 수정 + .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) // 나중에 수정 .type("place") .build(); } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 5d533438..9af733fe 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -35,10 +35,6 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; -// @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) -// @JoinColumn(name = "image_id", referencedColumnName = "id") -// private Image image; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview") private List images = new ArrayList<>(); From 936dde6d21076d93c2530179bbb462167b9644ab Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:44:09 +0900 Subject: [PATCH 369/516] =?UTF-8?q?Feat:=20Service=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EA=B5=AC=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceReviewRepository.java | 4 +++- .../service/PlaceReviewQueryServiceImpl.java | 13 ++++++++++++- .../route_item/repository/RouteItemRepository.java | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 05bed9af..a676ba34 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -1,7 +1,7 @@ package com.otakumap.domain.place_review.repository; import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.route.entity.Route; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,4 +10,6 @@ public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { Page findAllByUserId(Long userId, PageRequest pageRequest); void deleteAllByUserId(Long userId); + + PlaceReview findAllByRoute(Route route); } diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 0b3a234c..79e909fe 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -9,9 +9,13 @@ import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route_item.entity.RouteItem; +import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,6 +23,7 @@ import java.util.Map; import java.util.stream.Collectors; +@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -27,6 +32,7 @@ public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { private final PlaceRepository placeRepository; private final PlaceReviewRepository placeReviewRepository; private final PlaceShortReviewRepository placeShortReviewRepository; + private final RouteItemRepository routeItemRepository; @Override public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { @@ -43,7 +49,12 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); // 전체 리뷰 리스트 - List allReviews = placeReviewRepository.findAllReviewsByPlace(placeId, sort); + List routeItems = routeItemRepository.findAllByPlace(place); + List routes = routeItems.stream() + .map(RouteItem::getRoute) + .toList(); + List allReviews = routes.stream() + .map(placeReviewRepository::findAllByRoute).toList(); // 애니메이션별로 그룹화 Map> reviewsByAnimation = allReviews.stream() diff --git a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java index 93d1ee52..6df893e7 100644 --- a/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java +++ b/src/main/java/com/otakumap/domain/route_item/repository/RouteItemRepository.java @@ -18,4 +18,6 @@ public interface RouteItemRepository extends JpaRepository { List findPlacesByRouteId(@Param("routeId") Long routeId); List findByRouteId(Long routeId); + + List findAllByPlace(Place place); } From 260f050cf235a424671dc5eeede1f6fc221d602c Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 13 Feb 2025 00:56:44 +0900 Subject: [PATCH 370/516] =?UTF-8?q?feature:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=EB=8F=84=20=EB=B0=98=ED=99=98=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EA=B2=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/route/dto/RouteResponseDTO.java | 1 + .../otakumap/domain/route/service/RouteQueryServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java index 2653b80b..cf58bd35 100644 --- a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java @@ -26,6 +26,7 @@ public static class RouteDTO { @AllArgsConstructor public static class RouteDetailDTO { private Long routeId; + private String routeName; private List places; } } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 7adf52bc..377958e3 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -33,6 +33,6 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { // routeId로 Place 목록 조회 List places = PlaceConverter.toPlaceDTOList(routeItemRepository.findPlacesByRouteId(routeId)); - return new RouteResponseDTO.RouteDetailDTO(route.getId(), places); + return new RouteResponseDTO.RouteDetailDTO(route.getId(), route.getName(), places); } } \ No newline at end of file From 26ddf3ff4f9cc134a93463454a1c8908ff9d1c62 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 13 Feb 2025 01:05:28 +0900 Subject: [PATCH 371/516] =?UTF-8?q?feature:=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=A0=9C=EB=AA=A9=EB=8F=84=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceAnimationRepository.java | 2 ++ .../domain/route/dto/RouteResponseDTO.java | 1 + .../route/service/RouteQueryServiceImpl.java | 30 +++++++++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index 8837a058..1bcbda41 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place_animation.repository; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -10,4 +11,5 @@ public interface PlaceAnimationRepository extends JpaRepository findByIdAndPlaceId(Long id, Long placeId); List findByPlaceId(Long placeId); Optional findByPlaceIdAndAnimationId(Long placeId, Long animationId); + List findByPlaceIn(List places); } diff --git a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java index cf58bd35..698b5aa5 100644 --- a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java @@ -27,6 +27,7 @@ public static class RouteDTO { public static class RouteDetailDTO { private Long routeId; private String routeName; + private String animationName; private List places; } } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 377958e3..bd2b3be7 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -1,7 +1,10 @@ package com.otakumap.domain.route.service; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.converter.PlaceConverter; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; @@ -19,6 +22,7 @@ public class RouteQueryServiceImpl implements RouteQueryService { private final RouteRepository routeRepository; private final RouteItemRepository routeItemRepository; + private final PlaceAnimationRepository placeAnimationRepository; @Override public boolean isRouteExist(Long routeId) { @@ -30,9 +34,29 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { Route route = routeRepository.findById(routeId) .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); - // routeId로 Place 목록 조회 - List places = PlaceConverter.toPlaceDTOList(routeItemRepository.findPlacesByRouteId(routeId)); + // routeId에 해당하는 Place 목록 조회 + List places = routeItemRepository.findPlacesByRouteId(routeId); - return new RouteResponseDTO.RouteDetailDTO(route.getId(), route.getName(), places); + // 해당하는 Place에 대한 PlaceAnimation 조회 + List placeAnimations = placeAnimationRepository.findByPlaceIn(places); + + // Animation 이름 추출 (중복 제거) + List animationNames = placeAnimations.stream() + .map(pa -> pa.getAnimation().getName()) + .distinct() + .toList(); + + // 첫 번째 애니메이션 선택 (없으면 빈 문자열) + String animationName = animationNames.isEmpty() ? "" : animationNames.get(0); + + // PlaceDTO 변환 + List placeDTOs = PlaceConverter.toPlaceDTOList(places); + + return new RouteResponseDTO.RouteDetailDTO( + route.getId(), + route.getName(), + animationName, + placeDTOs + ); } } \ No newline at end of file From 334192cee0bd471ada6a98de21f8b9cf3fb8cf7f Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 13 Feb 2025 01:18:21 +0900 Subject: [PATCH 372/516] =?UTF-8?q?feature:=20=EA=B4=80=EB=A0=A8=EB=90=9C?= =?UTF-8?q?=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route/service/RouteQueryServiceImpl.java | 10 ++++++++-- .../global/apiPayload/code/status/ErrorStatus.java | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index bd2b3be7..2a433b2e 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -46,8 +46,14 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { .distinct() .toList(); - // 첫 번째 애니메이션 선택 (없으면 빈 문자열) - String animationName = animationNames.isEmpty() ? "" : animationNames.get(0); + + // 관련된 애니메이션이 없으면 예외 발생 + if (animationNames.isEmpty()) { + throw new RouteHandler(ErrorStatus.ROUTE_ANIMATION_NOT_FOUND); + } + + // 첫 번째 애니메이션 선택 + String animationName = animationNames.get(0); // PlaceDTO 변환 List placeDTOs = PlaceConverter.toPlaceDTOList(places); diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index dfac5d16..a24a38ec 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -72,6 +72,9 @@ public enum ErrorStatus implements BaseErrorCode { // 루트 관련 에러 ROUTE_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE4001", "존재하지 않은 루트입니다."), + // 루트 애니메이션 관련 에러 + ROUTE_ANIMATION_NOT_FOUND(HttpStatus.NOT_FOUND, "ROUTE_ANIMATION4001", "관련된 애니메이션이 존재하지 않습니다."), + // 루트 좋아요 관련 에러 ROUTE_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ROUTE4002", "이미 좋아요를 누른 루트입니다."), ROUTE_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "ROUTE4003", "저장되지 않은 루트입니다."), From 7b112eb4397a7ac15dd12ed2e05157cf4075974f Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 03:33:19 +0900 Subject: [PATCH 373/516] =?UTF-8?q?Feat:=20=ED=95=B4=EC=8B=9C=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20Animation?= =?UTF-8?q?ReviewGroupDTO=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_review/dto/PlaceReviewResponseDTO.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 709e0d84..5d7d2184 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -33,6 +33,7 @@ public static class PlaceReviewDTO { public static class AnimationReviewGroupDTO { private Long animationId; private String animationName; + private List hashTags; private List reviews; private long totalReviews; } @@ -46,8 +47,6 @@ public static class PlaceAnimationReviewDTO { private String placeName; private Float avgRating; private long totalReviews; - // place_hashtag -> place_animation_hashtag 변경에 따라 일단 주석 처리 - //private List hashTags; private List animationGroups; } From e3d6285fb4a69aaf8f4391f35a511735d8e46303 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 03:34:18 +0900 Subject: [PATCH 374/516] =?UTF-8?q?Feat:=20dto=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20Converter=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceReviewConverter.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 6883972d..1af6cc89 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -1,14 +1,12 @@ package com.otakumap.domain.place_review.converter; import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.user.entity.User; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -29,7 +27,8 @@ public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview } // 해당 장소의 애니메이션별 리뷰 그룹 생성 - public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGroupDTO(Animation animation, List reviews) { + public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGroupDTO(Animation animation, List reviews, + List hashTagDTOS) { List reviewDTOs = reviews.stream() .map(PlaceReviewConverter::toPlaceReviewDTO) @@ -39,6 +38,7 @@ public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGr .animationId(animation.getId()) .animationName(animation.getName()) .reviews(reviewDTOs) + .hashTags(hashTagDTOS) .totalReviews(reviews.size()) .build(); } @@ -47,19 +47,11 @@ public static PlaceReviewResponseDTO.AnimationReviewGroupDTO toAnimationReviewGr public static PlaceReviewResponseDTO.PlaceAnimationReviewDTO toPlaceAnimationReviewDTO(Place place, long totalReviews, List animationGroups, Float avgRating) { - - // place_hashtag -> place_animation_hashtag 변경에 따라 일단 주석 처리 - //List hashTagDTOs = place.getPlaceHashTagList() - // .stream() - // .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())) - // .toList(); - return PlaceReviewResponseDTO.PlaceAnimationReviewDTO.builder() .placeId(place.getId()) .placeName(place.getName()) .animationGroups(animationGroups) .totalReviews(totalReviews) - //.hashTags(hashTagDTOs) .avgRating(avgRating) .build(); } From 7ad20ca11264254d4412a550d7ca2fd59a9c0b3f Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 03:35:43 +0900 Subject: [PATCH 375/516] =?UTF-8?q?Feat:=20Service=EC=97=90=EC=84=9C=20Pla?= =?UTF-8?q?ceAnimation=EC=9D=84=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PlaceReviewQueryServiceImpl.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 79e909fe..142febeb 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -1,6 +1,9 @@ package com.otakumap.domain.place_review.service; import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_review.converter.PlaceReviewConverter; @@ -19,8 +22,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @Slf4j @@ -56,12 +61,31 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla List allReviews = routes.stream() .map(placeReviewRepository::findAllByRoute).toList(); - // 애니메이션별로 그룹화 + // 애니메이션별 해시태그 + List placeAnimations = allReviews.stream() + .map(PlaceReview::getPlaceAnimation) + .filter(Objects::nonNull) + .distinct() + .toList(); + Map> animationHashTagMap = + placeAnimations.stream() + .collect(Collectors.groupingBy( + PlaceAnimation::getAnimation, // 그룹의 키 : PlaceAnimation이 참조하는 Animation + + Collectors.flatMapping( + pa -> pa.getPlaceAnimationHashTags().stream() + .peek(pah -> log.info("PlaceAnimation 정보 : {}, HashTag : {}", pa.getAnimation().getName(), pah.getHashTag().getName())) + .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())), + Collectors.toList() + ) + )); + + // 애니메이션별로 리뷰 그룹화 Map> reviewsByAnimation = allReviews.stream() .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 - List animationGroups = paginateReviews(reviewsByAnimation, page, size); + List animationGroups = paginateReviews(reviewsByAnimation, animationHashTagMap, page, size); // 총 리뷰 수 계산 long totalReviews = reviewsByAnimation.values().stream() @@ -71,17 +95,23 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); } - private List paginateReviews(Map> reviewsByAnimation, int page, int size) { + private List paginateReviews(Map> reviewsByAnimation, + Map> animationHashTagMap, + int page, int size) { return reviewsByAnimation.entrySet().stream() .map(entry -> { + Animation animation = entry.getKey(); List reviews = entry.getValue(); + // 각 애니메이션에 해당하는 해시태그 목록을 가져오기 + List hashTagsForAnimation = animationHashTagMap.getOrDefault(animation, Collections.emptyList()); + int fromIndex = Math.min(page * size, reviews.size()); int toIndex = Math.min(fromIndex + size, reviews.size()); List pagedReviews = reviews.subList(fromIndex, toIndex); - return PlaceReviewConverter.toAnimationReviewGroupDTO(entry.getKey(), pagedReviews); + return PlaceReviewConverter.toAnimationReviewGroupDTO(animation, pagedReviews, hashTagsForAnimation); }) .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 .toList(); From 9da2d2cbd5beb035cc54f36317a516c2c571a5a4 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 05:25:01 +0900 Subject: [PATCH 376/516] =?UTF-8?q?Feat:=20QueryDSL=20=EA=B5=AC=EB=AC=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20PlaceReviewPlace=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceReviewPlaceRepository.java | 2 ++ .../domain/reviews/converter/ReviewConverter.java | 14 ++++++-------- .../reviews/repository/ReviewRepositoryImpl.java | 11 +++++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java index ec4844e2..e4ecdecc 100644 --- a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java @@ -1,7 +1,9 @@ package com.otakumap.domain.place_review_place.repository; import com.otakumap.domain.mapping.PlaceReviewPlace; +import com.otakumap.domain.place_review.entity.PlaceReview; import org.springframework.data.jpa.repository.JpaRepository; public interface PlaceReviewPlaceRepository extends JpaRepository { + PlaceReviewPlace findByPlaceReview(PlaceReview placeReview); } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 14a9e813..1bedeab1 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -4,7 +4,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; @@ -13,15 +12,14 @@ import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.route.converter.RouteConverter; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; -import com.otakumap.domain.route.converter.RouteConverter; -import com.otakumap.domain.route.entity.Route; - import java.util.Objects; +import java.util.stream.Collectors; public class ReviewConverter { @@ -64,11 +62,11 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr .build(); } - public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview) { + public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview, Long placeId) { + return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(placeReview.getId()) -// .id(placeReview.getPlace().getId()) - .id(placeReview.getPlaceList().get(0).getPlace().getId()) // 해령: 임시로 수정 + .id(placeId) .title(placeReview.getTitle()) .content(placeReview.getContent()) .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 00c0660a..573c0a6b 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -4,11 +4,14 @@ import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.entity.QEventReview; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.mapping.QEventAnimation; import com.otakumap.domain.mapping.QPlaceAnimation; +import com.otakumap.domain.mapping.QPlaceReviewPlace; import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; +import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.global.apiPayload.code.status.ErrorStatus; @@ -33,6 +36,7 @@ public class ReviewRepositoryImpl implements ReviewRepositoryCustom { private final JPAQueryFactory queryFactory; + private final PlaceReviewPlaceRepository placeReviewPlaceRepository; @Override public Page getReviewsByKeyword(String keyword, int page, int size, String sort) { @@ -41,6 +45,7 @@ public Page getReviewsByKeyword(Stri QEventAnimation eventAnimation = QEventAnimation.eventAnimation; QPlaceAnimation placeAnimation = QPlaceAnimation.placeAnimation; QAnimation animation = QAnimation.animation; + QPlaceReviewPlace placeReviewPlace = QPlaceReviewPlace.placeReviewPlace; // 이벤트 리뷰 검색 : EventReview 제목, 내용, 또는 연관된 애니메이션 이름 BooleanBuilder eventCondition = createSearchCondition(eventReview.title, eventReview.content, eventAnimation.animation.name, keyword); @@ -56,7 +61,8 @@ public Page getReviewsByKeyword(Stri .fetch(); List placeReviews = queryFactory.selectFrom(placeReview) -// .leftJoin(placeReview.place, QPlace.place) // 해령: placeReview와 place가 N:M 관계가 되어 주석 처리 + .leftJoin(placeReviewPlace).on(placeReviewPlace.placeReview.eq(placeReview)) + .leftJoin(placeReviewPlace.place, QPlace.place) .leftJoin(QPlace.place.placeAnimationList, placeAnimation) .leftJoin(placeAnimation.animation, animation) .where(placeCondition) @@ -69,7 +75,8 @@ public Page getReviewsByKeyword(Stri } for(PlaceReview review : placeReviews) { - searchedReviews.add(ReviewConverter.toSearchedPlaceReviewPreviewDTO(review)); + PlaceReviewPlace prp = placeReviewPlaceRepository.findByPlaceReview(review); + searchedReviews.add(ReviewConverter.toSearchedPlaceReviewPreviewDTO(review, prp.getId())); } if (searchedReviews.isEmpty()) { From 3b35eec5510d399d1a789eb5f316593bf252999d Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 05:42:48 +0900 Subject: [PATCH 377/516] =?UTF-8?q?Refactor:=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20PlaceReviewPlace=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PlaceReviewQueryServiceImpl.java | 21 +++++++------------ .../PlaceReviewPlaceRepository.java | 4 ++++ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 142febeb..0bc88c21 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -4,17 +4,16 @@ import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_review.converter.PlaceReviewConverter; import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; -import com.otakumap.domain.route.entity.Route; -import com.otakumap.domain.route_item.entity.RouteItem; -import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import lombok.RequiredArgsConstructor; @@ -22,10 +21,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; @Slf4j @@ -37,7 +33,7 @@ public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { private final PlaceRepository placeRepository; private final PlaceReviewRepository placeReviewRepository; private final PlaceShortReviewRepository placeShortReviewRepository; - private final RouteItemRepository routeItemRepository; + private final PlaceReviewPlaceRepository placeReviewPlaceRepository; @Override public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { @@ -54,12 +50,11 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); // 전체 리뷰 리스트 - List routeItems = routeItemRepository.findAllByPlace(place); - List routes = routeItems.stream() - .map(RouteItem::getRoute) + List placeReviewPlaces = placeReviewPlaceRepository.findByPlace(place); + List allReviews = placeReviewPlaces.stream() + .map(prp -> placeReviewRepository.findById(prp.getPlaceReview().getId())) + .flatMap(Optional::stream) .toList(); - List allReviews = routes.stream() - .map(placeReviewRepository::findAllByRoute).toList(); // 애니메이션별 해시태그 List placeAnimations = allReviews.stream() diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java index ec4844e2..eb0b1ef1 100644 --- a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java @@ -1,7 +1,11 @@ package com.otakumap.domain.place_review_place.repository; import com.otakumap.domain.mapping.PlaceReviewPlace; +import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface PlaceReviewPlaceRepository extends JpaRepository { + List findByPlace(Place place); } From 4a9c3007a3ca4c8575abc257c7530c3cdbf48acc Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 13 Feb 2025 18:22:43 +0900 Subject: [PATCH 378/516] Feat: Ensure unique placeAnimation and store placeAnimationId in placeReview --- .../domain/place_review/entity/PlaceReview.java | 2 ++ .../reviews/service/ReviewCommandServiceImpl.java | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 3c3e0d69..6a6b3faf 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -59,4 +59,6 @@ public class PlaceReview extends BaseEntity { private Route route; public void setPlaceList(List placeList) { this.placeList = placeList; } + + public void setPlaceAnimation(PlaceAnimation placeAnimation) { this.placeAnimation = placeAnimation; } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index dba302e0..529e24d6 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -11,6 +11,7 @@ import com.otakumap.domain.event_review_place.repository.EventReviewPlaceRepository; import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.mapping.EventReviewPlace; +import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; @@ -68,7 +69,7 @@ public ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDT List routeItems = createRouteItems(request.getRouteItems(), animation, route); routeItemRepository.saveAll(routeItems); - return saveReview(request, user, images, route); + return saveReview(request, user, images, route, animation); } // request의 장소 목록을 route item 객체로 변환 @@ -95,9 +96,10 @@ private ReviewType getItemType(Place place) { } // 장소 또는 이벤트에 애니메이션을 연결 - private void associateAnimationWithPlaceOrEvent(Place place, Animation animation, ReviewType itemType) { + private PlaceAnimation associateAnimationWithPlaceOrEvent(Place place, Animation animation, ReviewType itemType) { if (itemType == ReviewType.PLACE) { - placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation)); + return placeAnimationRepository.findByPlaceIdAndAnimationId(place.getId(), animation.getId()) + .orElseGet(() -> placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation))); // placeAnimation 반환 } else { // Place에 해당하는 Event 찾기 List eventLocations = eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()); @@ -108,6 +110,7 @@ private void associateAnimationWithPlaceOrEvent(Place place, Animation animation // Event 객체를 이용해 EventAnimation 생성 및 저장 eventAnimationRepository.save(ReviewConverter.toEventAnimation(event, animation)); + return null; // Event는 따로 ID 반환할 필요 없음 } } @@ -119,7 +122,7 @@ private Route saveRoute(String title) { } // 리뷰 저장 및 반환 - private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images, Route route) { + private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images, Route route, Animation animation) { List routeItems = routeItemRepository.findByRouteId(route.getId()); List places = routeItems.stream() @@ -131,8 +134,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO // 먼저 PlaceReview를 저장 PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); placeReview = placeReviewRepository.save(placeReview); - - // 저장된 PlaceReview를 기반으로 placeReviewPlaces 생성 + associateAnimationWithPlaceOrEvent(places.get(0), animation, ReviewType.PLACE); List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); placeReviewPlaceRepository.saveAll(placeReviewPlaces); From 1a297d870362249a93ccdeeaeda28fed64012ad4 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 13 Feb 2025 18:31:15 +0900 Subject: [PATCH 379/516] =?UTF-8?q?Fix:=20PlaceReview=EC=97=90=20PlaceAnim?= =?UTF-8?q?ationId=20=EC=A0=80=EC=9E=A5=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/service/ReviewCommandServiceImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 529e24d6..5520a596 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -134,7 +134,11 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO // 먼저 PlaceReview를 저장 PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); placeReview = placeReviewRepository.save(placeReview); - associateAnimationWithPlaceOrEvent(places.get(0), animation, ReviewType.PLACE); + + PlaceAnimation placeAnimation = associateAnimationWithPlaceOrEvent(places.get(0), animation, ReviewType.PLACE); + placeReview.setPlaceAnimation(placeAnimation); + placeReview = placeReviewRepository.save(placeReview); + List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); placeReviewPlaceRepository.saveAll(placeReviewPlaces); From d58b5fc075f4c987c414fcdd3f34f4802d50ed85 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 18:35:18 +0900 Subject: [PATCH 380/516] =?UTF-8?q?Refactor:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_review/service/PlaceReviewQueryServiceImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 0bc88c21..75590500 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -17,14 +17,12 @@ import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; -@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -69,7 +67,6 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla Collectors.flatMapping( pa -> pa.getPlaceAnimationHashTags().stream() - .peek(pah -> log.info("PlaceAnimation 정보 : {}, HashTag : {}", pa.getAnimation().getName(), pah.getHashTag().getName())) .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())), Collectors.toList() ) From c87f40e2493b4f7a878861817c0d0775e56b8ff9 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Thu, 13 Feb 2025 18:55:48 +0900 Subject: [PATCH 381/516] =?UTF-8?q?Feat:=20DTO=EC=97=90=EC=84=9C=20event/p?= =?UTF-8?q?lace=20id=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceReviewPlaceRepository.java | 6 ------ .../otakumap/domain/reviews/converter/ReviewConverter.java | 4 +--- .../com/otakumap/domain/reviews/dto/ReviewResponseDTO.java | 1 - .../domain/reviews/repository/ReviewRepositoryImpl.java | 6 +----- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java index 177586ed..0d78098b 100644 --- a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java @@ -1,19 +1,13 @@ package com.otakumap.domain.place_review_place.repository; import com.otakumap.domain.mapping.PlaceReviewPlace; - -import com.otakumap.domain.place_review.entity.PlaceReview; - import com.otakumap.domain.place.entity.Place; - import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface PlaceReviewPlaceRepository extends JpaRepository { - PlaceReviewPlace findByPlaceReview(PlaceReview placeReview); - List findByPlace(Place place); } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 1bedeab1..2e4c8262 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -52,7 +52,6 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPreviewDTO(EventReview eventReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(eventReview.getId()) - .id(eventReview.getEvent().getId()) .title(eventReview.getTitle()) .content(eventReview.getContent()) .reviewImage(ImageConverter.toImageDTO(!eventReview.getImages().isEmpty() ? eventReview.getImages().get(0) : null)) @@ -62,11 +61,10 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPr .build(); } - public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview, Long placeId) { + public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPreviewDTO(PlaceReview placeReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(placeReview.getId()) - .id(placeId) .title(placeReview.getTitle()) .content(placeReview.getContent()) .reviewImage(ImageConverter.toImageDTO(!placeReview.getImages().isEmpty() ? placeReview.getImages().get(0) : null)) diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 634216c0..bb5372ef 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -39,7 +39,6 @@ public static class Top7ReviewPreViewDTO { @AllArgsConstructor public static class SearchedReviewPreViewDTO { Long reviewId; // 검색된 Review의 id - Long id; // Event 또는 Place의 id String title; String content; String type; // "event" 또는 "place" diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 573c0a6b..e18013a9 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -4,14 +4,12 @@ import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.entity.QEventReview; -import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.mapping.QEventAnimation; import com.otakumap.domain.mapping.QPlaceAnimation; import com.otakumap.domain.mapping.QPlaceReviewPlace; import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; -import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.global.apiPayload.code.status.ErrorStatus; @@ -36,7 +34,6 @@ public class ReviewRepositoryImpl implements ReviewRepositoryCustom { private final JPAQueryFactory queryFactory; - private final PlaceReviewPlaceRepository placeReviewPlaceRepository; @Override public Page getReviewsByKeyword(String keyword, int page, int size, String sort) { @@ -75,8 +72,7 @@ public Page getReviewsByKeyword(Stri } for(PlaceReview review : placeReviews) { - PlaceReviewPlace prp = placeReviewPlaceRepository.findByPlaceReview(review); - searchedReviews.add(ReviewConverter.toSearchedPlaceReviewPreviewDTO(review, prp.getId())); + searchedReviews.add(ReviewConverter.toSearchedPlaceReviewPreviewDTO(review)); } if (searchedReviews.isEmpty()) { From 4697b2bfa3e386a28bf9bfc514eb4b99f0e1326c Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 13 Feb 2025 22:17:35 +0900 Subject: [PATCH 382/516] =?UTF-8?q?Fix:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/place_like/entity/PlaceLike.java | 4 ---- .../domain/place_like/repository/PlaceLikeRepository.java | 4 ---- .../domain/place_like/service/PlaceLikeQueryServiceImpl.java | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java index 1dd1acba..301a1cab 100644 --- a/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java +++ b/src/main/java/com/otakumap/domain/place_like/entity/PlaceLike.java @@ -26,10 +26,6 @@ public class PlaceLike extends BaseEntity { @JoinColumn(name = "user_id", nullable = false) private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_id", nullable = false) - private Place place; - @ManyToOne @JoinColumn(name = "place_animation_id", nullable = false) private PlaceAnimation placeAnimation; diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index d5b42168..1c8d30ab 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -5,10 +5,6 @@ import com.otakumap.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface PlaceLikeRepository extends JpaRepository { boolean existsByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); - // 특정 Place와 연결된 PlaceLike가 존재하는지 확인 - Optional findByPlaceIdAndUserId(Long placeId, Long userId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index ce44230e..e3ae44cb 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -39,7 +39,7 @@ public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, List result = jpaQueryFactory .selectFrom(qPlaceLike) - .leftJoin(qPlaceLike.place).fetchJoin() + .leftJoin(qPlaceLike.placeAnimation).fetchJoin() .leftJoin(qPlaceLike.user).fetchJoin() .where(predicate) .orderBy(qPlaceLike.createdAt.desc()) From cd31d23fe42f3cd92cd07fb9bdb1feaee6c91b9a Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 13 Feb 2025 22:27:40 +0900 Subject: [PATCH 383/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hash_tag/converter/HashTagConverter.java | 1 - .../domain/place/DTO/PlaceResponseDTO.java | 5 +-- .../place/converter/PlaceConverter.java | 31 +++++++++---------- .../place/service/PlaceQueryService.java | 2 +- .../place/service/PlaceQueryServiceImpl.java | 20 +++++------- .../route/controller/RouteController.java | 21 ++++++------- 6 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java b/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java index 5ecf08e6..2df214e6 100644 --- a/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java +++ b/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java @@ -4,7 +4,6 @@ import com.otakumap.domain.hash_tag.entity.HashTag; public class HashTagConverter { - public static HashTagResponseDTO.HashTagDTO toHashTagDTO(HashTag hashTag) { return HashTagResponseDTO.HashTagDTO.builder() .hashTagId(hashTag.getId()) diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index 37ca055e..2090b8b7 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -1,6 +1,7 @@ package com.otakumap.domain.place.DTO; import com.otakumap.domain.animation.dto.AnimationResponseDTO; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -39,8 +40,8 @@ public static class PlaceDetailDTO { private Double longitude; private Boolean isFavorite; private Boolean isLiked; - private PlaceResponseDTO.PlaceAnimationListDTO animationListDTO; - private List hashtags; + private String animationName; + private List hashtags; } @Builder diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index 06990f05..692202a4 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -1,6 +1,8 @@ package com.otakumap.domain.place.converter; import com.otakumap.domain.animation.dto.AnimationResponseDTO; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.entity.Place; @@ -28,7 +30,7 @@ public static PlaceResponseDTO.PlaceAnimationListDTO toPlaceAnimationListDTO(Lis .build(); } - public static PlaceResponseDTO.PlaceDetailDTO toPlaceDetailDTO(Place place, PlaceResponseDTO.PlaceAnimationListDTO animationListDTO, List hashtags, boolean isLiked) { + public static PlaceResponseDTO.PlaceDetailDTO toPlaceDetailDTO(Place place, PlaceAnimation placeAnimation, List hashtags, boolean isLiked) { return PlaceResponseDTO.PlaceDetailDTO.builder() .id(place.getId()) .name(place.getName()) @@ -36,25 +38,24 @@ public static PlaceResponseDTO.PlaceDetailDTO toPlaceDetailDTO(Place place, Plac .longitude(place.getLng()) .isFavorite(place.getIsFavorite()) .isLiked(isLiked) - .animationListDTO(animationListDTO) + .animationName(placeAnimation.getAnimation().getName()) .hashtags(hashtags) .build(); } - public static List toPlaceHashtagsDTO(List placeAnimations) { - return placeAnimations.stream() - .flatMap(placeAnimation -> placeAnimation.getPlaceAnimationHashTags().stream()) - .map(placeAnimationHashTag -> placeAnimationHashTag.getHashTag().getName()) - .collect(Collectors.toList()); + public static List toPlaceHashtagsDTO(PlaceAnimation placeAnimation) { + return placeAnimation.getPlaceAnimationHashTags() + .stream() + .map(placeAnimationHashTag -> HashTagConverter.toHashTagDTO(placeAnimationHashTag.getHashTag())).toList(); } public static PlaceResponseDTO.PlaceDTO toPlaceDTO(Place place) { - return new PlaceResponseDTO.PlaceDTO( - place.getId(), - place.getName(), - place.getLat(), - place.getLng() - ); + return PlaceResponseDTO.PlaceDTO.builder() + .id(place.getId()) + .name(place.getName()) + .latitude(place.getLat()) + .longitude(place.getLng()) + .build(); } public static List toPlaceDTOList(List places) { @@ -63,13 +64,11 @@ public static List toPlaceDTOList(List places) .collect(Collectors.toList()); } - public static PlaceResponseDTO.SearchedPlaceInfoDTO toSearchedPlaceInfoDTO(Place place, - List animationDTOs) { + public static PlaceResponseDTO.SearchedPlaceInfoDTO toSearchedPlaceInfoDTO(Place place, List animationDTOs) { return PlaceResponseDTO.SearchedPlaceInfoDTO.builder() .placeId(place.getId()) .name(place.getName()) .animations(animationDTOs) .build(); - } } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java index 8bd24d2b..7892b502 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryService.java @@ -9,5 +9,5 @@ public interface PlaceQueryService { List getPlaceAnimations(Long placeId); boolean isPlaceExist(Long placeId); - PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId); + PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId, Long animationId); } diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java index 079606e1..84f8ba31 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.service; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.converter.PlaceConverter; @@ -7,7 +8,6 @@ import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.route.repository.RouteRepository; -import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; @@ -24,7 +24,6 @@ public class PlaceQueryServiceImpl implements PlaceQueryService { private final PlaceRepository placeRepository; private final PlaceAnimationRepository placeAnimationRepository; - private final RouteItemRepository routeItemRepository; private final PlaceLikeRepository placeLikeRepository; private final RouteRepository routeRepository; @@ -41,28 +40,25 @@ public List getPlaceAnimations(Long placeId) { @Override @Transactional - public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId) { + public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, Long placeId, Long animationId) { // routeId가 존재하는지 먼저 확인 boolean routeExists = routeRepository.existsById(routeId); if (!routeExists) { throw new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND); } - // RouteItem을 통해 Place 조회 - Place place = routeItemRepository.findPlaceByRouteIdAndPlaceId(routeId, placeId) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + // Place 조회 + Place place = placeRepository.findById(placeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); // 애니메이션 관련 정보 조회 - List placeAnimations = placeAnimationRepository.findByPlaceId(placeId); - PlaceResponseDTO.PlaceAnimationListDTO animationListDTO = PlaceConverter.toPlaceAnimationListDTO(placeAnimations); + PlaceAnimation placeAnimation = placeAnimationRepository.findByPlaceIdAndAnimationId(placeId, animationId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); // 해시태그 정보 조회 - List hashtags = PlaceConverter.toPlaceHashtagsDTO(placeAnimations); - + List hashtags = PlaceConverter.toPlaceHashtagsDTO(placeAnimation); // 특정 사용자가 해당 Place를 좋아요 했는지 여부 확인 - boolean isLiked = placeLikeRepository.findByPlaceIdAndUserId(placeId, user.getId()).isPresent(); + boolean isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); - return PlaceConverter.toPlaceDetailDTO(place, animationListDTO, hashtags, isLiked); + return PlaceConverter.toPlaceDetailDTO(place, placeAnimation, hashtags, isLiked); } } diff --git a/src/main/java/com/otakumap/domain/route/controller/RouteController.java b/src/main/java/com/otakumap/domain/route/controller/RouteController.java index 8a18547d..1e5b5bdc 100644 --- a/src/main/java/com/otakumap/domain/route/controller/RouteController.java +++ b/src/main/java/com/otakumap/domain/route/controller/RouteController.java @@ -12,35 +12,34 @@ import io.swagger.v3.oas.annotations.Parameters; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/route") +@RequestMapping("/api/routes") @RequiredArgsConstructor @Validated public class RouteController { private final PlaceQueryService placeQueryService; private final RouteQueryService routeQueryService; - @Operation(summary = "루트 내 특정 장소 상세 정보 조회", description = "주어진 routeId와 placeId를 기반으로 특정 장소의 상세 정보를 불러옵니다.") - @GetMapping("{routeId}/{placeId}") + @Operation(summary = "루트 내 특정 장소 상세 정보 조회", description = "주어진 routeId, placeId, animationId를 기반으로 특정 장소의 상세 정보를 불러옵니다.") + @GetMapping("/{routeId}/places/{placeId}") @Parameters({ @Parameter(name = "routeId", description = "루트 ID"), - @Parameter(name = "placeId", description = "루트 내 특정 장소 ID") + @Parameter(name = "placeId", description = "루트 내 특정 장소 ID"), + @Parameter(name = "animationId", description = "루트의 애니메이션 ID") }) public ApiResponse getPlaceDetail( @CurrentUser User user, @PathVariable Long routeId, - @PathVariable Long placeId) { - PlaceResponseDTO.PlaceDetailDTO placeDetail = placeQueryService.getPlaceDetail(user, routeId, placeId); + @PathVariable Long placeId, + @RequestParam Long animationId) { + PlaceResponseDTO.PlaceDetailDTO placeDetail = placeQueryService.getPlaceDetail(user, routeId, placeId, animationId); return ApiResponse.onSuccess(placeDetail); } @Operation(summary = "루트 상세 정보 조회", description = "주어진 routeId를 기반으로 루트의 상세 정보를 불러옵니다.") - @GetMapping("{routeId}") + @GetMapping("/{routeId}") @Parameters({ @Parameter(name = "routeId", description = "루트 ID") }) From bf1559e0f09ba1be08a265f98941851da32fdff7 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 13 Feb 2025 22:58:11 +0900 Subject: [PATCH 384/516] =?UTF-8?q?Fix:=20=EC=9D=B8=EA=B8=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20API=20=EB=9E=9C?= =?UTF-8?q?=EB=8D=A4=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/repository/EventRepositoryImpl.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index 026ea5b9..2520c7fa 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -20,6 +20,7 @@ import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -34,11 +35,21 @@ public class EventRepositoryImpl implements EventRepositoryCustom { public List getPopularEvents() { QEvent event = QEvent.event; - List events = queryFactory.selectFrom(event) + List eventIds = queryFactory.select(event.id) .where(event.endDate.goe(LocalDate.now()) .and(event.startDate.loe(LocalDate.now()))) - .orderBy(Expressions.numberTemplate(Double.class, "function('rand')").asc()) - .limit(8) + .fetch(); + + List randomIds; + if(eventIds.size() <= 8) { + randomIds = eventIds; + } else { + Collections.shuffle(eventIds); + randomIds = eventIds.subList(0, 8); + } + + List events = queryFactory.selectFrom(event) + .where(event.id.in(randomIds)) .fetch(); return events.stream() From e10405e1c48bd0a446990afea985f73ab2e9a92d Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 13 Feb 2025 22:59:11 +0900 Subject: [PATCH 385/516] =?UTF-8?q?Fix:=20=ED=99=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EB=B0=B0=EB=84=88=20API=20=EB=9E=9C=EB=8D=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/repository/EventRepositoryImpl.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index 2520c7fa..715134c2 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -11,7 +11,6 @@ import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -61,18 +60,25 @@ public List getPopularEvents() { public ImageResponseDTO.ImageDTO getEventBanner() { QEvent event = QEvent.event; - Event targetEvent = queryFactory.selectFrom(event) + List targetEventIds = queryFactory.select(event.id) .where(event.endDate.goe(LocalDate.now()) .and(event.startDate.loe(LocalDate.now())) .and(event.thumbnailImage.isNotNull())) - .orderBy(Expressions.numberTemplate(Double.class, "function('rand')").asc()) - .fetchFirst(); + .fetch(); - if (targetEvent == null) { + Long targetEventId; + if(targetEventIds.isEmpty()) { return ImageResponseDTO.ImageDTO.builder() - .fileUrl("default_banner_url") - .build(); + .fileUrl("default_banner_url") + .build(); + } else if(targetEventIds.size() > 1) { + Collections.shuffle(targetEventIds); } + targetEventId = targetEventIds.get(0); + + Event targetEvent = queryFactory.selectFrom(event) + .where(event.id.eq(targetEventId)) + .fetchOne(); return ImageConverter.toImageDTO((targetEvent) .getThumbnailImage()); From 95bf9af4f48f98db55221fbe87d0da7e09dae1c4 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 13 Feb 2025 23:06:46 +0900 Subject: [PATCH 386/516] =?UTF-8?q?Fix:=20No=20sources=20given=20error=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/repository/EventRepositoryImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index 715134c2..6f6d18c2 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -35,6 +35,7 @@ public List getPopularEvents() { QEvent event = QEvent.event; List eventIds = queryFactory.select(event.id) + .from(event) .where(event.endDate.goe(LocalDate.now()) .and(event.startDate.loe(LocalDate.now()))) .fetch(); @@ -61,6 +62,7 @@ public ImageResponseDTO.ImageDTO getEventBanner() { QEvent event = QEvent.event; List targetEventIds = queryFactory.select(event.id) + .from(event) .where(event.endDate.goe(LocalDate.now()) .and(event.startDate.loe(LocalDate.now())) .and(event.thumbnailImage.isNotNull())) From a1c919bf6fd31cd337d080b8e8ec884941dc5165 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 01:11:10 +0900 Subject: [PATCH 387/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 7 +++++++ .../domain/user/dto/UserRequestDTO.java | 7 +++++++ .../com/otakumap/domain/user/entity/User.java | 4 ++++ .../user/service/UserCommandService.java | 1 + .../user/service/UserCommandServiceImpl.java | 18 ++++++++++++++++++ 5 files changed, 37 insertions(+) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index 387d1aca..d5d67520 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -99,4 +99,11 @@ public ApiResponse updateProfileImage( String profileImageUrl = userCommandService.updateProfileImage(user, profileImage); return ApiResponse.onSuccess("프로필 이미지가 성공적으로 변경되었습니다. URL: " + profileImageUrl); } + + @PatchMapping("/email") + @Operation(summary = "이메일 변경 API", description = "이메일 변경을 위한 인증 및 중복 확인 기능입니다.") + public ApiResponse changeEmail(@RequestBody @Valid UserRequestDTO.ChangeEmailDTO request, @CurrentUser User user) { + userCommandService.changeEmail(user, request); + return ApiResponse.onSuccess("이메일 변경이 성공적으로 완료되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index fa62e827..211a4263 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -51,4 +51,11 @@ public static class ResetPasswordDTO { @Schema(description = "password", example = "otakumap1234!") String passwordCheck; } + + @Getter + public static class ChangeEmailDTO { + @NotBlank(message = "이메일 입력은 필수 입니다.") + @Email(message = "이메일 형식이 올바르지 않습니다.") + String email; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 3e9ae8dd..64a4876d 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -91,4 +91,8 @@ public void setNotification(Integer type, boolean isEnabled) { public void setProfileImage(Image image) { this.profileImage = image; } + + public void updateEmail(String email) { + this.email = email; + } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java index 0cb717a9..d1d0c99c 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -10,4 +10,5 @@ public interface UserCommandService { void updateNotificationSettings(User user, UserRequestDTO.NotificationSettingsRequestDTO request); void resetPassword(UserRequestDTO.ResetPasswordDTO request); String updateProfileImage(User user, MultipartFile file); + void changeEmail(User user, UserRequestDTO.ChangeEmailDTO request); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 033a3ad8..14471238 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -1,5 +1,6 @@ package com.otakumap.domain.user.service; +import com.otakumap.domain.auth.service.MailService; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.user.dto.UserRequestDTO; @@ -9,12 +10,16 @@ import com.otakumap.global.apiPayload.exception.handler.AuthHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; import com.otakumap.global.util.EmailUtil; +import com.otakumap.global.util.RedisUtil; +import jakarta.mail.MessagingException; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.util.concurrent.TimeUnit; + @Service @RequiredArgsConstructor public class UserCommandServiceImpl implements UserCommandService { @@ -22,6 +27,8 @@ public class UserCommandServiceImpl implements UserCommandService { private final EmailUtil emailUtil; private final PasswordEncoder passwordEncoder; private final ImageCommandService imageCommandService; + private final MailService mailService; + private final RedisUtil redisUtil; @Override @Transactional @@ -77,4 +84,15 @@ public String updateProfileImage(User user, MultipartFile file) { userRepository.save(user); return image.getFileUrl(); } + + @Override + @Transactional + public void changeEmail(User user, UserRequestDTO.ChangeEmailDTO request) { + if (userRepository.existsByEmail(request.getEmail())) { + throw new UserHandler(ErrorStatus.EMAIL_ALREADY_EXISTS); + } + + user.updateEmail(request.getEmail()); + userRepository.save(user); + } } \ No newline at end of file From ceea21eb1e8a0ab2547b27d356cb4142a07d5484 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 01:14:54 +0900 Subject: [PATCH 388/516] =?UTF-8?q?Refactor:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=20=EC=84=A4=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/user/contoller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index d5d67520..a2970419 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -101,7 +101,7 @@ public ApiResponse updateProfileImage( } @PatchMapping("/email") - @Operation(summary = "이메일 변경 API", description = "이메일 변경을 위한 인증 및 중복 확인 기능입니다.") + @Operation(summary = "이메일 변경 API", description = "이메일 변경을 위한 기능입니다. 중복 확인 및 인증 API를 먼저 사용한 후 이용해주세요.") public ApiResponse changeEmail(@RequestBody @Valid UserRequestDTO.ChangeEmailDTO request, @CurrentUser User user) { userCommandService.changeEmail(user, request); return ApiResponse.onSuccess("이메일 변경이 성공적으로 완료되었습니다."); From 62a790420237cdcf627cea0c976dffae3395787f Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 03:15:19 +0900 Subject: [PATCH 389/516] =?UTF-8?q?Feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=88=84=EB=A5=B8=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=95=8C=EB=A6=BC,=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event/entity/Event.java | 2 ++ .../event/repository/EventRepository.java | 5 +++ .../converter/NotificationConverter.java | 11 ++++++ .../entity/enums/NotificationType.java | 7 ++-- .../service/EventSchedulerService.java | 36 +++++++++++++++++++ .../service/NotificationCommandService.java | 4 +++ .../NotificationCommandServiceImpl.java | 31 ++++++++++++++++ .../service/RouteLikeCommandServiceImpl.java | 8 +++++ 8 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/notification/service/EventSchedulerService.java diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index 2dc9b4e7..1d7d6764 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -79,4 +79,6 @@ public class Event extends BaseEntity { @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) private List eventHashTagList = new ArrayList<>(); + + public void startEvent() { this.status = EventStatus.IN_PROCESS; } } diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java index f47975b5..99e95d94 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java @@ -1,7 +1,12 @@ package com.otakumap.domain.event.repository; import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.enums.EventStatus; import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDate; +import java.util.List; + public interface EventRepository extends JpaRepository { + List findByStartDateAndStatus(LocalDate startDate, EventStatus status); } diff --git a/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java b/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java index 9a0f89bc..53868460 100644 --- a/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java +++ b/src/main/java/com/otakumap/domain/notification/converter/NotificationConverter.java @@ -2,6 +2,8 @@ import com.otakumap.domain.notification.dto.NotificationResponseDTO; import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.notification.entity.enums.NotificationType; +import com.otakumap.domain.user.entity.User; import java.util.List; import java.util.stream.Collectors; @@ -23,4 +25,13 @@ public static NotificationResponseDTO.NotificationListDTO notificationListDTO(Li .notifications(notifications.stream().map(NotificationConverter::notificationDTO).collect(Collectors.toList())) .build(); } + + public static Notification toNotification(User user, NotificationType type, String message, String url) { + return Notification.builder() + .user(user) + .type(type) + .message(message) + .url(url) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java b/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java index 36fa4d58..e695d62d 100644 --- a/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java +++ b/src/main/java/com/otakumap/domain/notification/entity/enums/NotificationType.java @@ -1,5 +1,8 @@ package com.otakumap.domain.notification.entity.enums; public enum NotificationType { - COMMUNITY_ACTIVITY, POST_SAVE, POST_SUPPORT, SERVICE_NOTICE -} + EVENT_STARTED, + ROOT_SAVED, + POST_SUPPORTED, + SERVICE_UPDATE +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/notification/service/EventSchedulerService.java b/src/main/java/com/otakumap/domain/notification/service/EventSchedulerService.java new file mode 100644 index 00000000..04c3f19b --- /dev/null +++ b/src/main/java/com/otakumap/domain/notification/service/EventSchedulerService.java @@ -0,0 +1,36 @@ +package com.otakumap.domain.notification.service; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.enums.EventStatus; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_like.entity.EventLike; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class EventSchedulerService { + + private final EventRepository eventRepository; + private final NotificationCommandService notificationCommandService; + + @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 + @Transactional + public void checkAndNotifyEventStart() { + List eventsStartingToday = eventRepository.findByStartDateAndStatus(LocalDate.now(), EventStatus.NOT_STARTED); + + for (Event event : eventsStartingToday) { + event.startEvent(); // 이벤트 상태 변경 (진행 예정 → 진행 중) + eventRepository.save(event); // 변경 사항 저장 + + // 좋아요한 사용자들에게 알림 전송 + for (EventLike like : event.getEventLikeList()) { + notificationCommandService.notifyEventStarted(like.getUser(), event.getId()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java index 20c7f45c..64d28d4e 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java @@ -1,5 +1,9 @@ package com.otakumap.domain.notification.service; +import com.otakumap.domain.user.entity.User; + public interface NotificationCommandService { void markAsRead(Long userId, Long notificationId); + void notifyEventStarted(User user, Long eventId); + void notifyRootSaved(User user, Long routeId, int rootCount); } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java index bf95cbdb..9aa208e5 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java @@ -1,7 +1,14 @@ package com.otakumap.domain.notification.service; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.notification.converter.NotificationConverter; import com.otakumap.domain.notification.entity.Notification; +import com.otakumap.domain.notification.entity.enums.NotificationType; import com.otakumap.domain.notification.repository.NotificationRepository; +import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.NotificationHandler; import jakarta.transaction.Transactional; @@ -12,6 +19,8 @@ @RequiredArgsConstructor public class NotificationCommandServiceImpl implements NotificationCommandService{ private final NotificationRepository notificationRepository; + private final EventRepository eventRepository; + private final RouteRepository routeRepository; @Override @Transactional @@ -30,4 +39,26 @@ public void markAsRead(Long userId, Long notificationId) { notification.setIsRead(true); notificationRepository.save(notification); } + + @Override + @Transactional + public void notifyEventStarted(User user, Long eventId) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotificationHandler(ErrorStatus.EVENT_NOT_FOUND)); + + String message = event.getName() + " 이벤트가 시작되었습니다! 지금 바로 확인하세요!"; + String url = "/api/events/" + eventId + "/details"; + notificationRepository.save(NotificationConverter.toNotification(user, NotificationType.EVENT_STARTED, message, url)); + } + + @Override + @Transactional + public void notifyRootSaved(User user, Long routeId, int rootCount) { + Route route = routeRepository.findById(routeId) + .orElseThrow(() -> new NotificationHandler(ErrorStatus.ROUTE_NOT_FOUND)); + + String message = route.getName() + "의 루트 저장이 " + rootCount + "를 돌파하였습니다!"; + String url = "/api/routes/" + routeId; + notificationRepository.save(NotificationConverter.toNotification(user, NotificationType.ROOT_SAVED, message, url)); + } } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 0ddb2896..b0e31ee4 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -1,5 +1,6 @@ package com.otakumap.domain.route_like.service; +import com.otakumap.domain.notification.service.NotificationCommandService; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.route.converter.RouteConverter; @@ -32,6 +33,7 @@ public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { private final RouteRepository routeRepository; private final EntityManager entityManager; private final PlaceRepository placeRepository; + private final NotificationCommandService notificationCommandService; @Override public void saveRouteLike(User user, Long routeId) { @@ -44,6 +46,12 @@ public void saveRouteLike(User user, Long routeId) { RouteLike routeLike = RouteLikeConverter.toRouteLike(user, route); routeLikeRepository.save(routeLike); + + // 작성자에게 알림 전송 + int likeCount = route.getRouteLikes().size(); + if ((likeCount <= 50 && likeCount % 10 == 0) || (likeCount > 50 && likeCount <= 100 && likeCount % 50 == 0) || (likeCount > 100 && likeCount % 100 == 0)) { + notificationCommandService.notifyRootSaved(user, routeId, likeCount); + } } @Transactional From b836d8a3272e56a63531242f127db407a0f90f65 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 03:18:04 +0900 Subject: [PATCH 390/516] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../route_like/service/RouteLikeCommandServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index b0e31ee4..2cac6ce2 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -49,7 +49,10 @@ public void saveRouteLike(User user, Long routeId) { // 작성자에게 알림 전송 int likeCount = route.getRouteLikes().size(); - if ((likeCount <= 50 && likeCount % 10 == 0) || (likeCount > 50 && likeCount <= 100 && likeCount % 50 == 0) || (likeCount > 100 && likeCount % 100 == 0)) { + if (likeCount <= 10 || + (likeCount <= 50 && likeCount % 10 == 0) || + (likeCount <= 100 && likeCount % 50 == 0) || + (likeCount % 100 == 0)) { notificationCommandService.notifyRootSaved(user, routeId, likeCount); } } From 5e7f7ce8f50b2e9672d1fe46652a17d76c02bb70 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 03:28:20 +0900 Subject: [PATCH 391/516] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=95=8C=EB=A6=BC=20message=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/service/NotificationCommandServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java index 9aa208e5..847373eb 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java @@ -57,7 +57,7 @@ public void notifyRootSaved(User user, Long routeId, int rootCount) { Route route = routeRepository.findById(routeId) .orElseThrow(() -> new NotificationHandler(ErrorStatus.ROUTE_NOT_FOUND)); - String message = route.getName() + "의 루트 저장이 " + rootCount + "를 돌파하였습니다!"; + String message = route.getName() + "의 루트 저장이 " + rootCount + "을(를) 돌파하였습니다!"; String url = "/api/routes/" + routeId; notificationRepository.save(NotificationConverter.toNotification(user, NotificationType.ROOT_SAVED, message, url)); } From 8f62820756925f090962546a45993ba91fa8aaf9 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 03:37:17 +0900 Subject: [PATCH 392/516] =?UTF-8?q?Fix:=20=EB=A3=A8=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90=EC=97=90=EA=B2=8C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventReviewRepository.java | 3 +++ .../repository/PlaceReviewRepository.java | 4 +++- .../service/RouteLikeCommandServiceImpl.java | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 605451cf..535c6673 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -6,6 +6,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface EventReviewRepository extends JpaRepository { Page findAllByEvent(Event event, PageRequest pageRequest); + Optional findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index a676ba34..ae214524 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -6,10 +6,12 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { Page findAllByUserId(Long userId, PageRequest pageRequest); void deleteAllByUserId(Long userId); - + Optional findByRouteId(Long routeId); PlaceReview findAllByRoute(Route route); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 2cac6ce2..dd6f8903 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -1,8 +1,12 @@ package com.otakumap.domain.route_like.service; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.notification.service.NotificationCommandService; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; @@ -34,6 +38,8 @@ public class RouteLikeCommandServiceImpl implements RouteLikeCommandService { private final EntityManager entityManager; private final PlaceRepository placeRepository; private final NotificationCommandService notificationCommandService; + private final PlaceReviewRepository placeReviewRepository; + private final EventReviewRepository eventReviewRepository; @Override public void saveRouteLike(User user, Long routeId) { @@ -53,7 +59,14 @@ public void saveRouteLike(User user, Long routeId) { (likeCount <= 50 && likeCount % 10 == 0) || (likeCount <= 100 && likeCount % 50 == 0) || (likeCount % 100 == 0)) { - notificationCommandService.notifyRootSaved(user, routeId, likeCount); + PlaceReview placeReview = placeReviewRepository.findByRouteId(routeId).orElse(null); + EventReview eventReview = eventReviewRepository.findByRouteId(routeId).orElse(null); + + User author = placeReview != null ? placeReview.getUser() : eventReview != null ? eventReview.getUser() : null; + + if (author != null) { + notificationCommandService.notifyRootSaved(author, routeId, likeCount); + } } } From 5d9657ec8b4fdfd6026603236c3c08ac34f55044 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 03:44:20 +0900 Subject: [PATCH 393/516] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/repository/EventReviewRepository.java | 7 ++++++- .../place_review/repository/PlaceReviewRepository.java | 8 +++++++- .../domain/route_like/repository/RouteLikeRepository.java | 1 + .../route_like/service/RouteLikeCommandServiceImpl.java | 7 +++---- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 535c6673..3eaced8e 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -2,13 +2,18 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; public interface EventReviewRepository extends JpaRepository { Page findAllByEvent(Event event, PageRequest pageRequest); - Optional findByRouteId(Long routeId); + + @Query("SELECT er.user FROM EventReview er WHERE er.route.id = :routeId") + Optional findUserByRouteId(@Param("routeId") Long routeId); } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index ae214524..f0510800 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -2,9 +2,12 @@ import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -12,6 +15,9 @@ public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { Page findAllByUserId(Long userId, PageRequest pageRequest); void deleteAllByUserId(Long userId); - Optional findByRouteId(Long routeId); + + @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.route.id = :routeId") + Optional findUserByRouteId(@Param("routeId") Long routeId); + PlaceReview findAllByRoute(Route route); } diff --git a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java index da9146c5..34931901 100644 --- a/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java +++ b/src/main/java/com/otakumap/domain/route_like/repository/RouteLikeRepository.java @@ -10,4 +10,5 @@ public interface RouteLikeRepository extends JpaRepository { boolean existsByUserAndRoute(User user, Route route); Optional findByUserAndRoute(User user, Route route); + int countByRoute(Route route); } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index dd6f8903..9f1f1cde 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -54,15 +54,14 @@ public void saveRouteLike(User user, Long routeId) { routeLikeRepository.save(routeLike); // 작성자에게 알림 전송 - int likeCount = route.getRouteLikes().size(); + int likeCount = routeLikeRepository.countByRoute(route); if (likeCount <= 10 || (likeCount <= 50 && likeCount % 10 == 0) || (likeCount <= 100 && likeCount % 50 == 0) || (likeCount % 100 == 0)) { - PlaceReview placeReview = placeReviewRepository.findByRouteId(routeId).orElse(null); - EventReview eventReview = eventReviewRepository.findByRouteId(routeId).orElse(null); - User author = placeReview != null ? placeReview.getUser() : eventReview != null ? eventReview.getUser() : null; + User author = placeReviewRepository.findUserByRouteId(routeId) + .orElseGet(() -> eventReviewRepository.findUserByRouteId(routeId).orElse(null)); if (author != null) { notificationCommandService.notifyRootSaved(author, routeId, likeCount); From be884e3b4faec41da0cdbf5a067ca7dd9c97fa5c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Fri, 14 Feb 2025 04:13:07 +0900 Subject: [PATCH 394/516] =?UTF-8?q?Feat:=20@Scheduled=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=B4=20@EnableScheduling=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/OtakumapApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/OtakumapApplication.java b/src/main/java/com/otakumap/OtakumapApplication.java index 504ffa81..44e9f3c9 100644 --- a/src/main/java/com/otakumap/OtakumapApplication.java +++ b/src/main/java/com/otakumap/OtakumapApplication.java @@ -4,9 +4,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; @EnableJpaAuditing @EnableAsync +@EnableScheduling @SpringBootApplication public class OtakumapApplication { From 695c3f82cb09a230c3742ff85d89d0e29542ff61 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 14 Feb 2025 22:03:11 +0900 Subject: [PATCH 395/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=ED=94=84=EB=A1=9C=ED=95=84=EB=A1=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthCommandServiceImpl.java | 7 +++++++ .../java/com/otakumap/domain/image/entity/Image.java | 9 ++++++++- src/main/java/com/otakumap/domain/user/entity/User.java | 9 ++++++--- .../global/apiPayload/code/status/ErrorStatus.java | 1 + 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java index 7b386047..0e8de689 100644 --- a/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/AuthCommandServiceImpl.java @@ -5,6 +5,8 @@ import com.otakumap.domain.auth.jwt.dto.JwtDTO; import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; import com.otakumap.domain.auth.jwt.util.JwtProvider; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.image.repository.ImageRepository; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; @@ -29,6 +31,7 @@ public class AuthCommandServiceImpl implements AuthCommandService { private final RedisUtil redisUtil; private final MailService mailService; private final JwtProvider jwtProvider; + private final ImageRepository imageRepository; @Override public User signup(AuthRequestDTO.SignupDTO request) { @@ -37,6 +40,10 @@ public User signup(AuthRequestDTO.SignupDTO request) { } User newUser = UserConverter.toUser(request); newUser.encodePassword(passwordEncoder.encode(request.getPassword())); + + // 기본 이미지는 항상 PK값을 1로 가져오도록 설정 + Image profileImage = imageRepository.findById(1L).orElseThrow(() -> new AuthHandler(ErrorStatus.IMAGE_NOT_FOUND)); + newUser.setProfileImage(profileImage); return userRepository.save(newUser); } diff --git a/src/main/java/com/otakumap/domain/image/entity/Image.java b/src/main/java/com/otakumap/domain/image/entity/Image.java index c733d55a..2fe9075c 100644 --- a/src/main/java/com/otakumap/domain/image/entity/Image.java +++ b/src/main/java/com/otakumap/domain/image/entity/Image.java @@ -1,18 +1,22 @@ package com.otakumap.domain.image.entity; import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Image extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -34,6 +38,9 @@ public class Image extends BaseEntity { @JoinColumn(name = "event_review_id") private EventReview eventReview; + @OneToMany(mappedBy = "profileImage", cascade = CascadeType.ALL) + private List users = new ArrayList<>(); + public void setPlaceReview(PlaceReview placeReview) { this.placeReview = placeReview; } public void setEventReview(EventReview eventReview) { this.eventReview = eventReview; } diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 3e9ae8dd..096004b4 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.place_like.entity.PlaceLike; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; @@ -53,11 +54,13 @@ public class User extends BaseEntity { @Column(nullable = false) private Integer donation; + @ColumnDefault("true") @Column(nullable = false) - private Boolean isCommunityActivityNotified = true; + private Boolean isCommunityActivityNotified; + @ColumnDefault("true") @Column(nullable = false) - private Boolean isEventBenefitsNotified = true; + private Boolean isEventBenefitsNotified; @Enumerated(EnumType.STRING) @Column(columnDefinition = "VARCHAR(10) DEFAULT 'ACTIVE'", nullable = false) @@ -67,7 +70,7 @@ public class User extends BaseEntity { @Column(columnDefinition = "VARCHAR(10) DEFAULT 'USER'", nullable = false) private Role role; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index dfac5d16..84924424 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -95,6 +95,7 @@ public enum ErrorStatus implements BaseErrorCode { // 이미지 관련 에러 INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), + IMAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "IMAGE4002", "존재하지 않는 이미지입니다."), // 검색 관련 에러 INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."); From db8970676234c35837240df70460594a7a056a54 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 14 Feb 2025 22:52:01 +0900 Subject: [PATCH 396/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventShortReviewController.java | 13 +++++++++++-- .../dto/EventShortReviewRequestDTO.java | 11 ++++++++++- .../event_short_review/entity/EventShortReview.java | 8 +++++++- .../service/EventShortReviewCommandService.java | 1 + .../service/EventShortReviewCommandServiceImpl.java | 10 +++++++++- .../global/apiPayload/code/status/ErrorStatus.java | 5 ++++- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java index f8d41cae..96a40d1e 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -21,9 +22,7 @@ @RequiredArgsConstructor @Validated public class EventShortReviewController { - private final EventShortReviewCommandService eventShortReviewCommandService; - private final EventShortReviewRepository eventShortReviewRepository; @Operation(summary = "이벤트 한 줄 리뷰 작성", description = "이벤트에 한 줄 리뷰를 작성합니다.") @PostMapping("/events/{eventId}/short-reviews") @@ -46,4 +45,14 @@ public ApiResponse createEve public ApiResponse getEventShortReviewList(@PathVariable(name = "eventId") Long eventId, @RequestParam(name = "page")Integer page) { return ApiResponse.onSuccess(EventShortReviewConverter.toEventShortReviewListDTO(eventShortReviewCommandService.getEventShortReviewsByEventId(eventId, page))); } + + @Operation(summary = "이벤트 한 줄 리뷰 수정", description = "이벤트 한 줄 리뷰의 별점과 내용을 수정합니다.") + @PatchMapping("/short-reviews/{eventShortReviewId}") + @Parameters({ + @Parameter(name = "eventShortReviewId", description = "특정 한 줄 리뷰의 Id") + }) + public ApiResponse updateEventShortReview(@PathVariable Long eventShortReviewId, @RequestBody @Valid EventShortReviewRequestDTO.UpdateEventShortReviewDTO request) { + eventShortReviewCommandService.updateEventShortReview(eventShortReviewId, request); + return ApiResponse.onSuccess("한 줄 리뷰가 성공적으로 수정되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java index 50448ff6..e1140f94 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewRequestDTO.java @@ -1,12 +1,21 @@ package com.otakumap.domain.event_short_review.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; public class EventShortReviewRequestDTO { - @Getter public static class NewEventShortReviewDTO { Float rating; String content; } + + @Getter + public static class UpdateEventShortReviewDTO { + @NotNull(message = "평점 입력은 필수입니다.") + Float rating; + @NotBlank(message = "내용 입력은 필수입니다.") + String content; + } } diff --git a/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java index 1ff7d94c..687f5a2f 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java +++ b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java @@ -17,7 +17,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class EventShortReview extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -42,4 +41,11 @@ public class EventShortReview extends BaseEntity { @ColumnDefault("0") private int dislikes; + public void setContent(String content) { + this.content = content; + } + + public void setRating(Float rating) { + this.rating = rating; + } } diff --git a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java index d2fcebee..20ddc4d8 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java @@ -8,4 +8,5 @@ public interface EventShortReviewCommandService { EventShortReview createEventShortReview(Long eventId, User user, EventShortReviewRequestDTO.NewEventShortReviewDTO request); Page getEventShortReviewsByEventId(Long eventId, Integer page); + void updateEventShortReview(Long eventShortReviewId, EventShortReviewRequestDTO.UpdateEventShortReviewDTO request); } diff --git a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java index 8fb99755..2558d71e 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java @@ -20,7 +20,6 @@ @RequiredArgsConstructor @Transactional public class EventShortReviewCommandServiceImpl implements EventShortReviewCommandService{ - private final UserRepository userRepository; private final EventShortReviewRepository eventShortReviewRepository; private final EventRepository eventRepository; @@ -42,4 +41,13 @@ public Page getEventShortReviewsByEventId(Long eventId, Intege return eventShortReviewRepository.findAllByEvent(event, PageRequest.of(page, 4)); } + + @Override + public void updateEventShortReview(Long eventShortReviewId, EventShortReviewRequestDTO.UpdateEventShortReviewDTO request) { + EventShortReview eventShortReview = eventShortReviewRepository.findById(eventShortReviewId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_SHORT_REVIEW_NOT_FOUND)); + + eventShortReview.setContent(request.getContent()); + eventShortReview.setRating(request.getRating()); + } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index dfac5d16..e4cbce9c 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -97,7 +97,10 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), // 검색 관련 에러 - INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."); + INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."), + + // 한 줄 리뷰 관련 에러 + EVENT_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4003", "존재하지 않는 이벤트 한 줄 리뷰입니다."); private final HttpStatus httpStatus; private final String code; From 328cb76a155aa3629fbd024b562274aeaa5fa4c4 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Fri, 14 Feb 2025 22:52:29 +0900 Subject: [PATCH 397/516] =?UTF-8?q?Feat:=20=EC=A7=80=EB=8F=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9E=A5=EC=86=8C=20=EB=B0=8F=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EB=B3=B4=20=EB=B3=B4=EA=B8=B0=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/repository/EventRepository.java | 12 ++++ .../domain/map/controller/MapController.java | 35 ++++++++++ .../domain/map/converter/MapConverter.java | 68 +++++++++++++++++++ .../domain/map/dto/MapResponseDTO.java | 51 ++++++++++++++ .../map/repository/MapRepositoryCustom.java | 7 ++ .../repository/MapRepositoryCustomImpl.java | 62 +++++++++++++++++ .../domain/map/service/MapCustomService.java | 7 ++ .../map/service/MapCustomServiceImpl.java | 20 ++++++ .../otakumap/domain/place/entity/Place.java | 5 ++ .../place/repository/PlaceRepository.java | 7 ++ .../PlaceReviewPlaceRepository.java | 2 - 11 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/map/controller/MapController.java create mode 100644 src/main/java/com/otakumap/domain/map/converter/MapConverter.java create mode 100644 src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java create mode 100644 src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java create mode 100644 src/main/java/com/otakumap/domain/map/service/MapCustomService.java create mode 100644 src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java index f47975b5..fb01cbf5 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java @@ -2,6 +2,18 @@ import com.otakumap.domain.event.entity.Event; 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 EventRepository extends JpaRepository { + @Query("SELECT e FROM Event e " + + "JOIN FETCH e.eventLocation el " + + "LEFT JOIN FETCH e.eventAnimationList ea " + + "WHERE el.lat = :latitude AND el.lng = :longitude") + List findEventsByLocationWithAnimations(@Param("latitude") Double latitude, + @Param("longitude") Double longitude); } + + diff --git a/src/main/java/com/otakumap/domain/map/controller/MapController.java b/src/main/java/com/otakumap/domain/map/controller/MapController.java new file mode 100644 index 00000000..487d3291 --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/controller/MapController.java @@ -0,0 +1,35 @@ +package com.otakumap.domain.map.controller; + +import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.map.service.MapCustomService; +import com.otakumap.global.apiPayload.ApiResponse; +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.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/map") +@RequiredArgsConstructor +@Validated +public class MapController { + + private final MapCustomService mapCustomService; + + @GetMapping("/details") + @Operation(summary = "지도에서 장소 및 이벤트 정보 보기", description = "장소의 Latitude, Longitude 수령해 해당 장소의 명소와 이벤트를 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "latitude"), + @Parameter(name = "longitude"), + }) + public ApiResponse getSearchedPlaceInfoList(@RequestParam Double latitude, + @RequestParam Double longitude) { + return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(latitude, longitude)); + } +} diff --git a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java new file mode 100644 index 00000000..80fcdc13 --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java @@ -0,0 +1,68 @@ +package com.otakumap.domain.map.converter; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.place.entity.Place; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class MapConverter { + + public static MapResponseDTO.MapDetailDTO toMapDetailDTO( + List eventList, + List placeList) { + return MapResponseDTO.MapDetailDTO.builder() + .events(eventList) + .places(placeList) + .build(); + } + + public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( + Event event, + String locationName, + Animation animation, + List hashTags) { + ImageResponseDTO.ImageDTO image = null; + if(event.getThumbnailImage() != null) { + image = ImageConverter.toImageDTO(event.getThumbnailImage()); + } + + return MapResponseDTO.MapDetailEventDTO.builder() + .type("event") + .id(event.getId()) + .name(event.getName()) + .thumbnail(image) + .endDate(event.getEndDate()) + .locationName(locationName) + .animationName(animation != null ? animation.getName() : "") + .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) + .build(); + } + + public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( + Place place, + Animation animation, + List hashTags) { + ImageResponseDTO.ImageDTO image = null; + if(place.getThumbnailImage() != null) { + image = ImageConverter.toImageDTO(place.getThumbnailImage()); + } + + return MapResponseDTO.MapDetailPlaceDTO.builder() + .type("place") + .id(place.getId()) + .name(place.getName()) + .detail(place.getDetail()) + .animationName(animation != null ? animation.getName() : "") + .thumbnail(image) + .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java new file mode 100644 index 00000000..89c336cf --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java @@ -0,0 +1,51 @@ +package com.otakumap.domain.map.dto; + +import com.otakumap.domain.image.dto.ImageResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +public class MapResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MapDetailDTO { + private List events; + private List places; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MapDetailEventDTO { + private String type; + private Long id; + private String name; + private LocalDate endDate; + private String locationName; + private String animationName; + private ImageResponseDTO.ImageDTO thumbnail; + private List hashtags; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class MapDetailPlaceDTO { + private String type; + private Long id; + private String name; + private String detail; + private String animationName; + private ImageResponseDTO.ImageDTO thumbnail; + private List hashtags; + } +} diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java new file mode 100644 index 00000000..50b1c1b3 --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.map.repository; + +import com.otakumap.domain.map.dto.MapResponseDTO; + +public interface MapRepositoryCustom { + MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude); +} diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java new file mode 100644 index 00000000..e6f6d1c4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java @@ -0,0 +1,62 @@ +package com.otakumap.domain.map.repository; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.map.converter.MapConverter; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.mapping.EventHashTag; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class MapRepositoryCustomImpl implements MapRepositoryCustom { + + private final EventRepository eventRepository; + private final PlaceRepository placeRepository; + + @Override + public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude) { + List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); + List eventDTOs = eventList.stream().map(event -> { + List eventHashTags = event.getEventHashTagList().stream() + .map(EventHashTag::getHashTag) + .collect(Collectors.toList()); + + List eventAnimations = event.getEventAnimationList().stream() + .map(EventAnimation::getAnimation) + .toList(); + + return MapConverter.toMapDetailEventDTO(event, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); + }).collect(Collectors.toList()); + + List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); + List placeDTOs = placeList.stream().map(place -> { + List placeHashTags = new ArrayList<>(); + place.getPlaceAnimationList().forEach(placeAnimation -> { + placeAnimation.getPlaceAnimationHashTags().forEach(paht -> { + placeHashTags.add(paht.getHashTag()); + }); + }); + + List placeAnimations = place.getPlaceAnimationList().stream() + .map(PlaceAnimation::getAnimation) + .toList(); + + return MapConverter.toMapDetailPlaceDTO(place, placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); + }).collect(Collectors.toList()); + + return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); + } + +} diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomService.java b/src/main/java/com/otakumap/domain/map/service/MapCustomService.java new file mode 100644 index 00000000..88d00abb --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.map.service; + +import com.otakumap.domain.map.dto.MapResponseDTO; + +public interface MapCustomService { + MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude); +} diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java new file mode 100644 index 00000000..48d9bf47 --- /dev/null +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.map.service; + +import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.map.repository.MapRepositoryCustom; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MapCustomServiceImpl implements MapCustomService { + + private final MapRepositoryCustom mapRepositoryCustom; + + @Override + public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude) { + return mapRepositoryCustom.findAllMapDetails(latitude, longitude); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 585b506c..7db66421 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.entity; +import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; @@ -41,6 +42,10 @@ public class Place extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "thumbnail_image_id", referencedColumnName = "id") + private Image thumbnailImage; + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List reviews = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java index 6819f675..24b05a18 100644 --- a/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place/repository/PlaceRepository.java @@ -2,6 +2,8 @@ import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -10,4 +12,9 @@ public interface PlaceRepository extends JpaRepository { List findByLatAndLng(Double lat, Double lng); Optional findByNameAndLatAndLng(String name, Double lat, Double lng); + @Query("SELECT p FROM Place p " + + "LEFT JOIN FETCH p.placeAnimationList pa " + + "LEFT JOIN FETCH pa.animation " + + "WHERE p.lat = :latitude AND p.lng = :longitude") + List findPlacesByLocationWithAnimations(@Param("latitude") Double latitude, @Param("longitude") Double longitude); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java index 0d78098b..eb0b1ef1 100644 --- a/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java +++ b/src/main/java/com/otakumap/domain/place_review_place/repository/PlaceReviewPlaceRepository.java @@ -7,7 +7,5 @@ import java.util.List; public interface PlaceReviewPlaceRepository extends JpaRepository { - List findByPlace(Place place); - } From fc4fe14ad2d1a81d35f71c37b691a509c3d44b99 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 14 Feb 2025 22:56:48 +0900 Subject: [PATCH 398/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventShortReviewController.java | 10 ++++++++++ .../service/EventShortReviewCommandService.java | 1 + .../service/EventShortReviewCommandServiceImpl.java | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java index 96a40d1e..69db15bc 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java @@ -55,4 +55,14 @@ public ApiResponse updateEventShortReview(@PathVariable Long eventShortR eventShortReviewCommandService.updateEventShortReview(eventShortReviewId, request); return ApiResponse.onSuccess("한 줄 리뷰가 성공적으로 수정되었습니다."); } + + @Operation(summary = "이벤트 한 줄 리뷰 삭제", description = "이벤트 한 줄 리뷰를 삭제합니다.") + @DeleteMapping("/short-reviews/{eventShortReviewId}") + @Parameters({ + @Parameter(name = "eventShortReviewId", description = "특정 한 줄 리뷰의 Id") + }) + public ApiResponse deleteEventShortReview(@PathVariable Long eventShortReviewId) { + eventShortReviewCommandService.deleteEventShortReview(eventShortReviewId); + return ApiResponse.onSuccess("한 줄 리뷰가 성공적으로 삭제되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java index 20ddc4d8..a26062ef 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandService.java @@ -9,4 +9,5 @@ public interface EventShortReviewCommandService { EventShortReview createEventShortReview(Long eventId, User user, EventShortReviewRequestDTO.NewEventShortReviewDTO request); Page getEventShortReviewsByEventId(Long eventId, Integer page); void updateEventShortReview(Long eventShortReviewId, EventShortReviewRequestDTO.UpdateEventShortReviewDTO request); + void deleteEventShortReview(Long eventShortReviewId); } diff --git a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java index 2558d71e..1c8a391e 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_short_review/service/EventShortReviewCommandServiceImpl.java @@ -50,4 +50,12 @@ public void updateEventShortReview(Long eventShortReviewId, EventShortReviewRequ eventShortReview.setContent(request.getContent()); eventShortReview.setRating(request.getRating()); } + + @Override + public void deleteEventShortReview(Long eventShortReviewId) { + EventShortReview eventShortReview = eventShortReviewRepository.findById(eventShortReviewId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_SHORT_REVIEW_NOT_FOUND)); + + eventShortReviewRepository.delete(eventShortReview); + } } From f61fd8f0137a1eeab26008df6eabc860ba50f8c4 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 14 Feb 2025 23:47:42 +0900 Subject: [PATCH 399/516] =?UTF-8?q?Chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20DTO=20to=20dto=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{DTO => dto}/PlaceShortReviewRequestDTO.java | 0 .../{DTO => dto}/PlaceShortReviewResponseDTO.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/main/java/com/otakumap/domain/place_short_review/{DTO => dto}/PlaceShortReviewRequestDTO.java (100%) rename src/main/java/com/otakumap/domain/place_short_review/{DTO => dto}/PlaceShortReviewResponseDTO.java (100%) diff --git a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java similarity index 100% rename from src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewRequestDTO.java rename to src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java diff --git a/src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java similarity index 100% rename from src/main/java/com/otakumap/domain/place_short_review/DTO/PlaceShortReviewResponseDTO.java rename to src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java From faf6e0e2b26169278e5c7504513de5673d569d94 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 15 Feb 2025 00:02:17 +0900 Subject: [PATCH 400/516] =?UTF-8?q?Feat:=20=EB=AA=85=EC=86=8C=20=ED=95=9C?= =?UTF-8?q?=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=88=98=EC=A0=95=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 16 +++++++++++++--- .../converter/PlaceShortReviewConverter.java | 4 ++-- .../dto/PlaceShortReviewRequestDTO.java | 10 +++++++++- .../dto/PlaceShortReviewResponseDTO.java | 2 +- .../entity/PlaceShortReview.java | 8 ++++++++ .../service/PlaceShortReviewCommandService.java | 3 ++- .../PlaceShortReviewCommandServiceImpl.java | 15 +++++++++++---- .../apiPayload/code/status/ErrorStatus.java | 3 ++- 8 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index b4135f28..3b676e52 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -1,10 +1,10 @@ package com.otakumap.domain.place_short_review.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewResponseDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewResponseDTO; import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; import com.otakumap.domain.place_short_review.service.PlaceShortReviewQueryService; -import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.service.PlaceShortReviewCommandService; import com.otakumap.domain.user.entity.User; @@ -43,7 +43,7 @@ public ApiResponse getPlace } @PostMapping("/places/{placeId}/short-review") - @Operation(summary = "특정 명소의 한 줄 리뷰 목록 작성 API", description = "특정 명소의 한 줄 리뷰를 작성하는 API입니다. PlaceAnimationId는 특정 명소 관련 애니메이션 조회 API를 통해 얻을 수 있습니다.") + @Operation(summary = "특정 명소의 한 줄 리뷰 작성 API", description = "특정 명소의 한 줄 리뷰를 작성하는 API입니다. PlaceAnimationId는 특정 명소 관련 애니메이션 조회 API를 통해 얻을 수 있습니다.") public ApiResponse createReview( @CurrentUser User user, @PathVariable Long placeId, @@ -51,4 +51,14 @@ public ApiResponse createReview( PlaceShortReview placeShortReview = placeShortReviewCommandService.createReview(user, placeId, request); return ApiResponse.onSuccess(PlaceShortReviewConverter.toCreateReviewDTO(placeShortReview)); } + + @Operation(summary = "명소 한 줄 리뷰 수정", description = "명소 한 줄 리뷰의 별점과 내용을 수정합니다.") + @PatchMapping("/places/short-reviews/{placeShortReviewId}") + @Parameters({ + @Parameter(name = "placeShortReviewId", description = "특정 한 줄 리뷰의 Id") + }) + public ApiResponse updatePlaceShortReview(@PathVariable Long placeShortReviewId, @RequestBody @Valid PlaceShortReviewRequestDTO.UpdatePlaceShortReviewDTO request) { + placeShortReviewCommandService.updatePlaceShortReview(placeShortReviewId, request); + return ApiResponse.onSuccess("한 줄 리뷰가 성공적으로 수정되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index c7565237..a7a1b2a9 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -2,8 +2,8 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; -import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewResponseDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewResponseDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java index 43210cf8..1dd9aa67 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewRequestDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.place_short_review.DTO; +package com.otakumap.domain.place_short_review.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -14,4 +14,12 @@ public static class CreateDTO { @NotBlank String content; } + + @Getter + public static class UpdatePlaceShortReviewDTO { + @NotNull(message = "평점 입력은 필수입니다.") + Float rating; + @NotBlank(message = "내용 입력은 필수입니다.") + String content; + } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java index dcfd99cf..f9809dac 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.place_short_review.DTO; +package com.otakumap.domain.place_short_review.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java index 61836749..b9d4b292 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java +++ b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java @@ -44,4 +44,12 @@ public class PlaceShortReview extends BaseEntity { public void updateLikes(Long likes) { this.likes = likes; } public void updateDislikes(Long dislikes) { this.dislikes = dislikes; } + + public void setContent(String content) { + this.content = content; + } + + public void setRating(Float rating) { + this.rating = rating; + } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java index 7c21e111..ccabcc21 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java @@ -1,9 +1,10 @@ package com.otakumap.domain.place_short_review.service; -import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.user.entity.User; public interface PlaceShortReviewCommandService { PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); + void updatePlaceShortReview(Long placeShortReviewId, PlaceShortReviewRequestDTO.UpdatePlaceShortReviewDTO request); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index f0b4250c..583611b7 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -5,27 +5,25 @@ import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; -import com.otakumap.domain.place_short_review.DTO.PlaceShortReviewRequestDTO; +import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; import com.otakumap.domain.user.entity.User; -import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Transactional public class PlaceShortReviewCommandServiceImpl implements PlaceShortReviewCommandService { private final PlaceShortReviewRepository placeShortReviewRepository;; private final PlaceRepository placeRepository; private final PlaceAnimationRepository placeAnimationRepository; @Override - @Transactional public PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request) { Place place = placeRepository.findById(placeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); @@ -37,4 +35,13 @@ public PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRe return placeShortReviewRepository.save(newReview); } + + @Override + public void updatePlaceShortReview(Long placeShortReviewId, PlaceShortReviewRequestDTO.UpdatePlaceShortReviewDTO request) { + PlaceShortReview placeShortReview = placeShortReviewRepository.findById(placeShortReviewId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_SHORT_REVIEW_NOT_FOUND)); + + placeShortReview.setContent(request.getContent()); + placeShortReview.setRating(request.getRating()); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index e4cbce9c..01ec868c 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -100,7 +100,8 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "SEARCH4001", "유효하지 않은 검색어입니다."), // 한 줄 리뷰 관련 에러 - EVENT_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4003", "존재하지 않는 이벤트 한 줄 리뷰입니다."); + EVENT_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4008", "존재하지 않는 이벤트 한 줄 리뷰입니다."), + PLACE_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4006", "존재하지 않는 이벤트 한 줄 리뷰입니다."),; private final HttpStatus httpStatus; private final String code; From a0dcb8793f4ed76b07c2a9dacc684dc1082e7eb9 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 15 Feb 2025 00:02:49 +0900 Subject: [PATCH 401/516] =?UTF-8?q?Refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventShortReviewController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java index 69db15bc..301cc988 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java +++ b/src/main/java/com/otakumap/domain/event_short_review/controller/EventShortReviewController.java @@ -47,7 +47,7 @@ public ApiResponse getEvent } @Operation(summary = "이벤트 한 줄 리뷰 수정", description = "이벤트 한 줄 리뷰의 별점과 내용을 수정합니다.") - @PatchMapping("/short-reviews/{eventShortReviewId}") + @PatchMapping("/events/short-reviews/{eventShortReviewId}") @Parameters({ @Parameter(name = "eventShortReviewId", description = "특정 한 줄 리뷰의 Id") }) @@ -57,7 +57,7 @@ public ApiResponse updateEventShortReview(@PathVariable Long eventShortR } @Operation(summary = "이벤트 한 줄 리뷰 삭제", description = "이벤트 한 줄 리뷰를 삭제합니다.") - @DeleteMapping("/short-reviews/{eventShortReviewId}") + @DeleteMapping("/events/short-reviews/{eventShortReviewId}") @Parameters({ @Parameter(name = "eventShortReviewId", description = "특정 한 줄 리뷰의 Id") }) From 2baa2d26d8070720e50ccc6debf26c63c94bb24d Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 15 Feb 2025 00:08:02 +0900 Subject: [PATCH 402/516] =?UTF-8?q?Feat:=20=EB=AA=85=EC=86=8C=20=ED=95=9C?= =?UTF-8?q?=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EC=82=AD=EC=A0=9C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceShortReviewController.java | 10 ++++++++++ .../service/PlaceShortReviewCommandService.java | 1 + .../service/PlaceShortReviewCommandServiceImpl.java | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 3b676e52..5761ec46 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -61,4 +61,14 @@ public ApiResponse updatePlaceShortReview(@PathVariable Long placeShortR placeShortReviewCommandService.updatePlaceShortReview(placeShortReviewId, request); return ApiResponse.onSuccess("한 줄 리뷰가 성공적으로 수정되었습니다."); } + + @Operation(summary = "명소 한 줄 리뷰 삭제", description = "명소 한 줄 리뷰를 삭제합니다.") + @DeleteMapping("/places/short-reviews/{placeShortReviewId}") + @Parameters({ + @Parameter(name = "placeShortReviewId", description = "특정 한 줄 리뷰의 Id") + }) + public ApiResponse deletePlaceShortReview(@PathVariable Long placeShortReviewId) { + placeShortReviewCommandService.deletePlaceShortReview(placeShortReviewId); + return ApiResponse.onSuccess("한 줄 리뷰가 성공적으로 삭제되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java index ccabcc21..947b9241 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java @@ -7,4 +7,5 @@ public interface PlaceShortReviewCommandService { PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); void updatePlaceShortReview(Long placeShortReviewId, PlaceShortReviewRequestDTO.UpdatePlaceShortReviewDTO request); + void deletePlaceShortReview(Long placeShortReviewId); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index 583611b7..785ccc0d 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -44,4 +44,12 @@ public void updatePlaceShortReview(Long placeShortReviewId, PlaceShortReviewRequ placeShortReview.setContent(request.getContent()); placeShortReview.setRating(request.getRating()); } + + @Override + public void deletePlaceShortReview(Long placeShortReviewId) { + PlaceShortReview placeShortReview = placeShortReviewRepository.findById(placeShortReviewId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_SHORT_REVIEW_NOT_FOUND)); + + placeShortReviewRepository.delete(placeShortReview); + } } \ No newline at end of file From a4f554c2f26390c48ac6d6116408335d8c9c093e Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 15 Feb 2025 00:30:36 +0900 Subject: [PATCH 403/516] =?UTF-8?q?Feat:=20=EC=A7=80=EB=8F=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9E=A5=EC=86=8C=20=EB=B0=8F=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EB=B3=B4=20=EB=B3=B4=EA=B8=B0=20api?= =?UTF-8?q?=EC=97=90=20=EC=B0=9C=20=EC=97=AC=EB=B6=80=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/map/controller/MapController.java | 11 ++++- .../domain/map/converter/MapConverter.java | 4 ++ .../domain/map/dto/MapResponseDTO.java | 2 + .../map/repository/MapRepositoryCustom.java | 2 + .../repository/MapRepositoryCustomImpl.java | 43 ++++++++++++++++++- .../domain/map/service/MapCustomService.java | 2 + .../map/service/MapCustomServiceImpl.java | 6 +++ 7 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/map/controller/MapController.java b/src/main/java/com/otakumap/domain/map/controller/MapController.java index 487d3291..e581c5f9 100644 --- a/src/main/java/com/otakumap/domain/map/controller/MapController.java +++ b/src/main/java/com/otakumap/domain/map/controller/MapController.java @@ -1,7 +1,9 @@ package com.otakumap.domain.map.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.map.dto.MapResponseDTO; import com.otakumap.domain.map.service.MapCustomService; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -28,8 +30,13 @@ public class MapController { @Parameter(name = "latitude"), @Parameter(name = "longitude"), }) - public ApiResponse getSearchedPlaceInfoList(@RequestParam Double latitude, + public ApiResponse getSearchedPlaceInfoList( + @CurrentUser User user, + @RequestParam Double latitude, @RequestParam Double longitude) { - return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(latitude, longitude)); + if(user == null) { + return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(latitude, longitude)); + } + return ApiResponse.onSuccess(mapCustomService.findAllMapDetailsWithFavorite(user, latitude, longitude)); } } diff --git a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java index 80fcdc13..20990179 100644 --- a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java +++ b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java @@ -26,6 +26,7 @@ public static MapResponseDTO.MapDetailDTO toMapDetailDTO( public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( Event event, + Boolean isFavorite, String locationName, Animation animation, List hashTags) { @@ -40,6 +41,7 @@ public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( .name(event.getName()) .thumbnail(image) .endDate(event.getEndDate()) + .isFavorite(isFavorite) .locationName(locationName) .animationName(animation != null ? animation.getName() : "") .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) @@ -48,6 +50,7 @@ public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( Place place, + Boolean isFavorite, Animation animation, List hashTags) { ImageResponseDTO.ImageDTO image = null; @@ -60,6 +63,7 @@ public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( .id(place.getId()) .name(place.getName()) .detail(place.getDetail()) + .isFavorite(isFavorite) .animationName(animation != null ? animation.getName() : "") .thumbnail(image) .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) diff --git a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java index 89c336cf..6bd65125 100644 --- a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java +++ b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java @@ -29,6 +29,7 @@ public static class MapDetailEventDTO { private Long id; private String name; private LocalDate endDate; + private Boolean isFavorite; private String locationName; private String animationName; private ImageResponseDTO.ImageDTO thumbnail; @@ -44,6 +45,7 @@ public static class MapDetailPlaceDTO { private Long id; private String name; private String detail; + private Boolean isFavorite; private String animationName; private ImageResponseDTO.ImageDTO thumbnail; private List hashtags; diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java index 50b1c1b3..9194096f 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java @@ -1,7 +1,9 @@ package com.otakumap.domain.map.repository; import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.user.entity.User; public interface MapRepositoryCustom { MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude); + MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude); } diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java index e6f6d1c4..81b9decf 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java @@ -2,6 +2,7 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.map.converter.MapConverter; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; @@ -11,6 +12,8 @@ import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.hash_tag.entity.HashTag; import com.otakumap.domain.mapping.EventHashTag; +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import com.otakumap.domain.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -24,6 +27,8 @@ public class MapRepositoryCustomImpl implements MapRepositoryCustom { private final EventRepository eventRepository; private final PlaceRepository placeRepository; + private final EventLikeRepository eventLikeRepository; + private final PlaceLikeRepository placeLikeRepository; @Override public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude) { @@ -37,7 +42,7 @@ public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double lon .map(EventAnimation::getAnimation) .toList(); - return MapConverter.toMapDetailEventDTO(event, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); + return MapConverter.toMapDetailEventDTO(event, Boolean.FALSE, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); }).collect(Collectors.toList()); List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); @@ -53,10 +58,44 @@ public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double lon .map(PlaceAnimation::getAnimation) .toList(); - return MapConverter.toMapDetailPlaceDTO(place, placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); + return MapConverter.toMapDetailPlaceDTO(place, Boolean.FALSE, placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); }).collect(Collectors.toList()); return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); } + @Override + public MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude) { + List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); + List eventDTOs = eventList.stream().map(event -> { + List eventHashTags = event.getEventHashTagList().stream() + .map(EventHashTag::getHashTag) + .collect(Collectors.toList()); + + List eventAnimations = event.getEventAnimationList().stream() + .map(EventAnimation::getAnimation) + .toList(); + + return MapConverter.toMapDetailEventDTO(event, eventLikeRepository.existsByUserAndEvent(user, event), event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); + }).collect(Collectors.toList()); + + List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); + List placeDTOs = new ArrayList<>(); + + placeList.forEach(place -> { + List placeHashTags = new ArrayList<>(); + List placeAnimations = place.getPlaceAnimationList(); + + if (!placeAnimations.isEmpty()) { + placeAnimations.forEach(placeAnimation -> { + placeAnimation.getPlaceAnimationHashTags().forEach(paht -> { + placeHashTags.add(paht.getHashTag()); + }); + + placeDTOs.add(MapConverter.toMapDetailPlaceDTO(place, placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation), placeAnimation.getAnimation(), placeHashTags)); + }); + } + }); + return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); + } } diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomService.java b/src/main/java/com/otakumap/domain/map/service/MapCustomService.java index 88d00abb..4b712745 100644 --- a/src/main/java/com/otakumap/domain/map/service/MapCustomService.java +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomService.java @@ -1,7 +1,9 @@ package com.otakumap.domain.map.service; import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.user.entity.User; public interface MapCustomService { MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude); + MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude); } diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java index 48d9bf47..8177ecdb 100644 --- a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java @@ -2,6 +2,7 @@ import com.otakumap.domain.map.dto.MapResponseDTO; import com.otakumap.domain.map.repository.MapRepositoryCustom; +import com.otakumap.domain.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,4 +18,9 @@ public class MapCustomServiceImpl implements MapCustomService { public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude) { return mapRepositoryCustom.findAllMapDetails(latitude, longitude); } + + @Override + public MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude) { + return mapRepositoryCustom.findAllMapDetailsWithFavorite(user, latitude, longitude); + } } \ No newline at end of file From 776c01811dc83e809433988afa8bd65c984c45b4 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sat, 15 Feb 2025 01:08:57 +0900 Subject: [PATCH 404/516] =?UTF-8?q?feature:=20placeReview=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=ED=99=9C=EC=9A=A9=ED=95=B4=EC=84=9C=20ani?= =?UTF-8?q?mation=20=EC=A0=95=EB=B3=B4=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EA=B1=B8=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceReviewRepository.java | 4 ++- .../domain/route/dto/RouteResponseDTO.java | 1 + .../route/service/RouteQueryServiceImpl.java | 31 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 05bed9af..4fa5f29a 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -1,13 +1,15 @@ package com.otakumap.domain.place_review.repository; import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { Page findAllByUserId(Long userId, PageRequest pageRequest); void deleteAllByUserId(Long userId); + Optional findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java index 698b5aa5..13baafb3 100644 --- a/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route/dto/RouteResponseDTO.java @@ -27,6 +27,7 @@ public static class RouteDTO { public static class RouteDetailDTO { private Long routeId; private String routeName; + private Long animationId; private String animationName; private List places; } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 2a433b2e..aa0d69e2 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -1,16 +1,18 @@ package com.otakumap.domain.route.service; -import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.converter.PlaceConverter; import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import com.otakumap.global.apiPayload.exception.handler.RouteHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,8 +23,8 @@ @RequiredArgsConstructor public class RouteQueryServiceImpl implements RouteQueryService { private final RouteRepository routeRepository; + private final PlaceReviewRepository placeReviewRepository; private final RouteItemRepository routeItemRepository; - private final PlaceAnimationRepository placeAnimationRepository; @Override public boolean isRouteExist(Long routeId) { @@ -37,23 +39,17 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { // routeId에 해당하는 Place 목록 조회 List places = routeItemRepository.findPlacesByRouteId(routeId); - // 해당하는 Place에 대한 PlaceAnimation 조회 - List placeAnimations = placeAnimationRepository.findByPlaceIn(places); - - // Animation 이름 추출 (중복 제거) - List animationNames = placeAnimations.stream() - .map(pa -> pa.getAnimation().getName()) - .distinct() - .toList(); + // routeId에 해당하는 PlaceReview 또는 EventReview 조회 + PlaceReview placeReview = placeReviewRepository.findByRouteId(routeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); + Animation animation = null; + if (placeReview.getPlaceAnimation() != null) + animation = placeReview.getPlaceAnimation().getAnimation(); // 관련된 애니메이션이 없으면 예외 발생 - if (animationNames.isEmpty()) { + if (animation == null) throw new RouteHandler(ErrorStatus.ROUTE_ANIMATION_NOT_FOUND); - } - - // 첫 번째 애니메이션 선택 - String animationName = animationNames.get(0); // PlaceDTO 변환 List placeDTOs = PlaceConverter.toPlaceDTOList(places); @@ -61,7 +57,8 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { return new RouteResponseDTO.RouteDetailDTO( route.getId(), route.getName(), - animationName, + animation.getId(), + animation.getName(), placeDTOs ); } From 9d6fbba82c4e5d4f2c92b4f035d6f7febbb82daf Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 15 Feb 2025 14:19:36 +0900 Subject: [PATCH 405/516] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EB=9E=9C?= =?UTF-8?q?=EB=8D=A4=20=EC=83=9D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/user/converter/UserConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 90aa62e3..a824249e 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -18,6 +18,7 @@ public class UserConverter { public static User toUser(AuthRequestDTO.SignupDTO request) { return User.builder() .name(request.getName()) + .nickname(UuidGenerator.generateUuid()) .userId(request.getUserId()) .email(request.getEmail()) .password(request.getPassword()) From 0620af05511adbfd4617f39b73f2ccc81c97520d Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sat, 15 Feb 2025 15:15:28 +0900 Subject: [PATCH 406/516] =?UTF-8?q?refactor:=20=EC=95=88=20=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20converter=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PlaceAnimationRepository.java | 2 -- .../domain/route/converter/RouteConverter.java | 12 ++++++++++++ .../domain/route/service/RouteQueryServiceImpl.java | 11 +++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java index 1bcbda41..8837a058 100644 --- a/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java +++ b/src/main/java/com/otakumap/domain/place_animation/repository/PlaceAnimationRepository.java @@ -1,7 +1,6 @@ package com.otakumap.domain.place_animation.repository; import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.place.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -11,5 +10,4 @@ public interface PlaceAnimationRepository extends JpaRepository findByIdAndPlaceId(Long id, Long placeId); List findByPlaceId(Long placeId); Optional findByPlaceIdAndAnimationId(Long placeId, Long animationId); - List findByPlaceIn(List places); } diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index c3a5fbed..ccbea18e 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -1,5 +1,7 @@ package com.otakumap.domain.route.converter; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.converter.RouteItemConverter; @@ -30,4 +32,14 @@ public static Route toRoute(String name, List routeItems) { return route; } + + public static RouteResponseDTO.RouteDetailDTO toRouteDetailDTO(Route route, Animation animation, List placeDTOs) { + return new RouteResponseDTO.RouteDetailDTO( + route.getId(), + route.getName(), + animation.getId(), + animation.getName(), + placeDTOs + ); + } } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index aa0d69e2..888d1083 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -6,6 +6,7 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.dto.RouteResponseDTO; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; @@ -39,7 +40,7 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { // routeId에 해당하는 Place 목록 조회 List places = routeItemRepository.findPlacesByRouteId(routeId); - // routeId에 해당하는 PlaceReview 또는 EventReview 조회 + // routeId에 해당하는 PlaceReview 조회 PlaceReview placeReview = placeReviewRepository.findByRouteId(routeId) .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); @@ -54,12 +55,6 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { // PlaceDTO 변환 List placeDTOs = PlaceConverter.toPlaceDTOList(places); - return new RouteResponseDTO.RouteDetailDTO( - route.getId(), - route.getName(), - animation.getId(), - animation.getName(), - placeDTOs - ); + return RouteConverter.toRouteDetailDTO(route, animation, placeDTOs); } } \ No newline at end of file From a10b861c46c3d46f05919b37de9eb4e65c292fa6 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sat, 15 Feb 2025 15:40:10 +0900 Subject: [PATCH 407/516] =?UTF-8?q?Fix:=20Redis=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/global/config/RedisConfig.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/global/config/RedisConfig.java b/src/main/java/com/otakumap/global/config/RedisConfig.java index df813134..d19d3aad 100644 --- a/src/main/java/com/otakumap/global/config/RedisConfig.java +++ b/src/main/java/com/otakumap/global/config/RedisConfig.java @@ -4,6 +4,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisPassword; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -16,9 +18,17 @@ public class RedisConfig { @Value("${spring.data.redis.port}") private int port; + @Value("${spring.data.redis.password}") + private String password; + @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(host, port); + RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); + config.setHostName(host); + config.setPort(port); + config.setPassword(RedisPassword.of(password)); + + return new LettuceConnectionFactory(config); } @Bean From ee3fc1b35d5891ba635ca02f3c1f196a73498ea2 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sat, 15 Feb 2025 16:17:22 +0900 Subject: [PATCH 408/516] =?UTF-8?q?Feat:=20RouteId=EB=A5=BC=20PlaceReview?= =?UTF-8?q?=20=EB=98=90=EB=8A=94=20EventReview=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=AC=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventReviewRepository.java | 3 +++ .../route/service/RouteQueryServiceImpl.java | 25 ++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 605451cf..535c6673 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -6,6 +6,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface EventReviewRepository extends JpaRepository { Page findAllByEvent(Event event, PageRequest pageRequest); + Optional findByRouteId(Long routeId); } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 888d1083..2ac19bf0 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -1,6 +1,8 @@ package com.otakumap.domain.route.service; import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.place.DTO.PlaceResponseDTO; import com.otakumap.domain.place.converter.PlaceConverter; import com.otakumap.domain.place.entity.Place; @@ -13,12 +15,13 @@ import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.apiPayload.exception.handler.RouteHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -26,6 +29,7 @@ public class RouteQueryServiceImpl implements RouteQueryService { private final RouteRepository routeRepository; private final PlaceReviewRepository placeReviewRepository; private final RouteItemRepository routeItemRepository; + private final EventReviewRepository eventReviewRepository; @Override public boolean isRouteExist(Long routeId) { @@ -40,13 +44,22 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { // routeId에 해당하는 Place 목록 조회 List places = routeItemRepository.findPlacesByRouteId(routeId); - // routeId에 해당하는 PlaceReview 조회 - PlaceReview placeReview = placeReviewRepository.findByRouteId(routeId) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); + // placeReview 또는 eventReview 조회 + Optional placeReviewOpt = placeReviewRepository.findByRouteId(routeId); + Optional eventReviewOpt = eventReviewRepository.findByRouteId(routeId); + + // placeReview와 eventReview가 모두 없으면 예외 발생 + if (placeReviewOpt.isEmpty() && eventReviewOpt.isEmpty()) + throw new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND); Animation animation = null; - if (placeReview.getPlaceAnimation() != null) - animation = placeReview.getPlaceAnimation().getAnimation(); + if (placeReviewOpt.isPresent()) { + animation = placeReviewOpt.get().getPlaceAnimation() != null ? + placeReviewOpt.get().getPlaceAnimation().getAnimation() : null; + } else { + animation = eventReviewOpt.get().getEventAnimation() != null ? + eventReviewOpt.get().getEventAnimation().getAnimation() : null; + } // 관련된 애니메이션이 없으면 예외 발생 if (animation == null) From e05e409a879c340d521c16890ddbd40ba6ab4f6c Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 15 Feb 2025 16:32:21 +0900 Subject: [PATCH 409/516] =?UTF-8?q?Style:=20isFavorite=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20isLiked=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/map/converter/MapConverter.java | 8 ++++---- .../otakumap/domain/map/dto/MapResponseDTO.java | 4 ++-- .../map/repository/MapRepositoryCustomImpl.java | 15 ++++++++------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java index 20990179..fd97b43e 100644 --- a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java +++ b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java @@ -26,7 +26,7 @@ public static MapResponseDTO.MapDetailDTO toMapDetailDTO( public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( Event event, - Boolean isFavorite, + Boolean isLiked, String locationName, Animation animation, List hashTags) { @@ -41,7 +41,7 @@ public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( .name(event.getName()) .thumbnail(image) .endDate(event.getEndDate()) - .isFavorite(isFavorite) + .isLiked(isLiked) .locationName(locationName) .animationName(animation != null ? animation.getName() : "") .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) @@ -50,7 +50,7 @@ public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( Place place, - Boolean isFavorite, + Boolean isLiked, Animation animation, List hashTags) { ImageResponseDTO.ImageDTO image = null; @@ -63,7 +63,7 @@ public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( .id(place.getId()) .name(place.getName()) .detail(place.getDetail()) - .isFavorite(isFavorite) + .isLiked(isLiked) .animationName(animation != null ? animation.getName() : "") .thumbnail(image) .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) diff --git a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java index 6bd65125..dc8cbade 100644 --- a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java +++ b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java @@ -29,7 +29,7 @@ public static class MapDetailEventDTO { private Long id; private String name; private LocalDate endDate; - private Boolean isFavorite; + private Boolean isLiked; private String locationName; private String animationName; private ImageResponseDTO.ImageDTO thumbnail; @@ -45,7 +45,7 @@ public static class MapDetailPlaceDTO { private Long id; private String name; private String detail; - private Boolean isFavorite; + private Boolean isLiked; private String animationName; private ImageResponseDTO.ImageDTO thumbnail; private List hashtags; diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java index 81b9decf..13c98b2b 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java @@ -49,8 +49,8 @@ public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double lon List placeDTOs = placeList.stream().map(place -> { List placeHashTags = new ArrayList<>(); place.getPlaceAnimationList().forEach(placeAnimation -> { - placeAnimation.getPlaceAnimationHashTags().forEach(paht -> { - placeHashTags.add(paht.getHashTag()); + placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { + placeHashTags.add(hashTag.getHashTag()); }); }); @@ -75,8 +75,8 @@ public MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Doub List eventAnimations = event.getEventAnimationList().stream() .map(EventAnimation::getAnimation) .toList(); - - return MapConverter.toMapDetailEventDTO(event, eventLikeRepository.existsByUserAndEvent(user, event), event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); + Boolean isLiked = eventLikeRepository.existsByUserAndEvent(user, event); + return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); }).collect(Collectors.toList()); List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); @@ -88,11 +88,12 @@ public MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Doub if (!placeAnimations.isEmpty()) { placeAnimations.forEach(placeAnimation -> { - placeAnimation.getPlaceAnimationHashTags().forEach(paht -> { - placeHashTags.add(paht.getHashTag()); + placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { + placeHashTags.add(hashTag.getHashTag()); }); + Boolean isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); - placeDTOs.add(MapConverter.toMapDetailPlaceDTO(place, placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation), placeAnimation.getAnimation(), placeHashTags)); + placeDTOs.add(MapConverter.toMapDetailPlaceDTO(place, isLiked, placeAnimation.getAnimation(), placeHashTags)); }); } }); From 3128a114bb849fac6991c48bbac89159bc45096b Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sat, 15 Feb 2025 19:32:01 +0900 Subject: [PATCH 410/516] =?UTF-8?q?Feat:=20=ED=98=84=EC=9E=AC=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EC=A4=91=EC=9D=B8=20event=EB=A7=8C=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/event/repository/EventRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java index fb01cbf5..ac96ba07 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepository.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepository.java @@ -11,7 +11,7 @@ public interface EventRepository extends JpaRepository { @Query("SELECT e FROM Event e " + "JOIN FETCH e.eventLocation el " + "LEFT JOIN FETCH e.eventAnimationList ea " + - "WHERE el.lat = :latitude AND el.lng = :longitude") + "WHERE el.lat = :latitude AND el.lng = :longitude AND e.endDate >= CURDATE()") List findEventsByLocationWithAnimations(@Param("latitude") Double latitude, @Param("longitude") Double longitude); } From 6cae01c7436c1ff322a08c7e56ac815c583f79f3 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Sat, 15 Feb 2025 23:25:16 +0900 Subject: [PATCH 411/516] =?UTF-8?q?Fix:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_review/repository/EventReviewRepository.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 3edbaadb..58d2bc09 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -11,11 +11,9 @@ import java.util.Optional; -import java.util.Optional; - public interface EventReviewRepository extends JpaRepository { Page findAllByEvent(Event event, PageRequest pageRequest); Optional findByRouteId(Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.route.id = :routeId") - Optional findUserByRouteId(@Param("routeId") Long routeId + Optional findUserByRouteId(@Param("routeId") Long routeId); } From b97c2aa0a867c88ba0c7816cc88202f0b5c9e189 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 00:12:59 +0900 Subject: [PATCH 412/516] =?UTF-8?q?Refactor:=20Place=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20Service=20method?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 1 - .../domain/map/controller/MapController.java | 5 +- .../domain/map/converter/MapConverter.java | 6 --- .../domain/map/dto/MapResponseDTO.java | 1 - .../map/repository/MapRepositoryCustom.java | 3 +- .../repository/MapRepositoryCustomImpl.java | 54 +++++-------------- .../domain/map/service/MapCustomService.java | 3 +- .../map/service/MapCustomServiceImpl.java | 9 +--- .../otakumap/domain/place/entity/Place.java | 4 -- 9 files changed, 19 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 5a4e7918..15cf5cfb 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -9,7 +9,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/otakumap/domain/map/controller/MapController.java b/src/main/java/com/otakumap/domain/map/controller/MapController.java index e581c5f9..0362ccbc 100644 --- a/src/main/java/com/otakumap/domain/map/controller/MapController.java +++ b/src/main/java/com/otakumap/domain/map/controller/MapController.java @@ -34,9 +34,6 @@ public ApiResponse getSearchedPlaceInfoList( @CurrentUser User user, @RequestParam Double latitude, @RequestParam Double longitude) { - if(user == null) { - return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(latitude, longitude)); - } - return ApiResponse.onSuccess(mapCustomService.findAllMapDetailsWithFavorite(user, latitude, longitude)); + return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(user, latitude, longitude)); } } diff --git a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java index fd97b43e..ad70230a 100644 --- a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java +++ b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java @@ -53,11 +53,6 @@ public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( Boolean isLiked, Animation animation, List hashTags) { - ImageResponseDTO.ImageDTO image = null; - if(place.getThumbnailImage() != null) { - image = ImageConverter.toImageDTO(place.getThumbnailImage()); - } - return MapResponseDTO.MapDetailPlaceDTO.builder() .type("place") .id(place.getId()) @@ -65,7 +60,6 @@ public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( .detail(place.getDetail()) .isLiked(isLiked) .animationName(animation != null ? animation.getName() : "") - .thumbnail(image) .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) .build(); } diff --git a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java index dc8cbade..4e308509 100644 --- a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java +++ b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java @@ -47,7 +47,6 @@ public static class MapDetailPlaceDTO { private String detail; private Boolean isLiked; private String animationName; - private ImageResponseDTO.ImageDTO thumbnail; private List hashtags; } } diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java index 9194096f..f15ef3e2 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustom.java @@ -4,6 +4,5 @@ import com.otakumap.domain.user.entity.User; public interface MapRepositoryCustom { - MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude); - MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude); + MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude); } diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java index 13c98b2b..2c35c7c8 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @Repository @@ -31,9 +32,10 @@ public class MapRepositoryCustomImpl implements MapRepositoryCustom { private final PlaceLikeRepository placeLikeRepository; @Override - public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude) { + public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); List eventDTOs = eventList.stream().map(event -> { + Boolean isLiked = Boolean.FALSE; List eventHashTags = event.getEventHashTagList().stream() .map(EventHashTag::getHashTag) .collect(Collectors.toList()); @@ -41,62 +43,34 @@ public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double lon List eventAnimations = event.getEventAnimationList().stream() .map(EventAnimation::getAnimation) .toList(); - - return MapConverter.toMapDetailEventDTO(event, Boolean.FALSE, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); + if(user!=null) { + isLiked = eventLikeRepository.existsByUserAndEvent(user, event); + } + return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); }).collect(Collectors.toList()); List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); List placeDTOs = placeList.stream().map(place -> { List placeHashTags = new ArrayList<>(); + AtomicReference isLiked = new AtomicReference<>(Boolean.FALSE); + place.getPlaceAnimationList().forEach(placeAnimation -> { placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { placeHashTags.add(hashTag.getHashTag()); }); + + if(user!=null) { + isLiked.set(placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation)); + } }); List placeAnimations = place.getPlaceAnimationList().stream() .map(PlaceAnimation::getAnimation) .toList(); - return MapConverter.toMapDetailPlaceDTO(place, Boolean.FALSE, placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); - }).collect(Collectors.toList()); - - return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); - } - - @Override - public MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude) { - List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); - List eventDTOs = eventList.stream().map(event -> { - List eventHashTags = event.getEventHashTagList().stream() - .map(EventHashTag::getHashTag) - .collect(Collectors.toList()); - - List eventAnimations = event.getEventAnimationList().stream() - .map(EventAnimation::getAnimation) - .toList(); - Boolean isLiked = eventLikeRepository.existsByUserAndEvent(user, event); - return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); + return MapConverter.toMapDetailPlaceDTO(place, isLiked.get(), placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); }).collect(Collectors.toList()); - List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); - List placeDTOs = new ArrayList<>(); - - placeList.forEach(place -> { - List placeHashTags = new ArrayList<>(); - List placeAnimations = place.getPlaceAnimationList(); - - if (!placeAnimations.isEmpty()) { - placeAnimations.forEach(placeAnimation -> { - placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { - placeHashTags.add(hashTag.getHashTag()); - }); - Boolean isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); - - placeDTOs.add(MapConverter.toMapDetailPlaceDTO(place, isLiked, placeAnimation.getAnimation(), placeHashTags)); - }); - } - }); return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); } } diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomService.java b/src/main/java/com/otakumap/domain/map/service/MapCustomService.java index 4b712745..1c951a67 100644 --- a/src/main/java/com/otakumap/domain/map/service/MapCustomService.java +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomService.java @@ -4,6 +4,5 @@ import com.otakumap.domain.user.entity.User; public interface MapCustomService { - MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude); - MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude); + MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude); } diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java index 8177ecdb..b140742b 100644 --- a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java @@ -15,12 +15,7 @@ public class MapCustomServiceImpl implements MapCustomService { private final MapRepositoryCustom mapRepositoryCustom; @Override - public MapResponseDTO.MapDetailDTO findAllMapDetails(Double latitude, Double longitude) { - return mapRepositoryCustom.findAllMapDetails(latitude, longitude); - } - - @Override - public MapResponseDTO.MapDetailDTO findAllMapDetailsWithFavorite(User user, Double latitude, Double longitude) { - return mapRepositoryCustom.findAllMapDetailsWithFavorite(user, latitude, longitude); + public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { + return mapRepositoryCustom.findAllMapDetails(user, latitude, longitude); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place/entity/Place.java b/src/main/java/com/otakumap/domain/place/entity/Place.java index 7db66421..e629c8a2 100644 --- a/src/main/java/com/otakumap/domain/place/entity/Place.java +++ b/src/main/java/com/otakumap/domain/place/entity/Place.java @@ -42,10 +42,6 @@ public class Place extends BaseEntity { @ColumnDefault("false") private Boolean isFavorite; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "thumbnail_image_id", referencedColumnName = "id") - private Image thumbnailImage; - @OneToMany(mappedBy = "place", cascade = CascadeType.ALL) private List reviews = new ArrayList<>(); From 092cfd98fa521e935e7db5a770d45c1aa4629726 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 13:19:29 +0900 Subject: [PATCH 413/516] =?UTF-8?q?Refactor:=20Event=20Review=20Service=20?= =?UTF-8?q?=EB=AA=85=EC=B9=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/controller/EventReviewController.java | 6 +++--- .../event_review/repository/EventReviewRepository.java | 4 ++-- ...wCommandServivce.java => EventReviewCommandService.java} | 2 +- ...ServivceImpl.java => EventReviewCommandServiceImpl.java} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/main/java/com/otakumap/domain/event_review/service/{EventReviewCommandServivce.java => EventReviewCommandService.java} (83%) rename src/main/java/com/otakumap/domain/event_review/service/{EventReviewCommandServivceImpl.java => EventReviewCommandServiceImpl.java} (92%) diff --git a/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java b/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java index 516a7a9a..5c948a6a 100644 --- a/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java +++ b/src/main/java/com/otakumap/domain/event_review/controller/EventReviewController.java @@ -3,7 +3,7 @@ import com.otakumap.domain.event_review.converter.EventReviewConverter; import com.otakumap.domain.event_review.dto.EventReviewResponseDTO; -import com.otakumap.domain.event_review.service.EventReviewCommandServivce; +import com.otakumap.domain.event_review.service.EventReviewCommandService; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -19,7 +19,7 @@ @RequestMapping("/api") public class EventReviewController { - private final EventReviewCommandServivce eventReviewCommandServivce; + private final EventReviewCommandService eventReviewCommandService; @GetMapping("/events/{eventId}/reviews") @Operation(summary = "특정 이벤트의 후기 목록 조회", description = "특정 이벤트의 후기 목록(4개씩)을 불러옵니다.") @@ -31,6 +31,6 @@ public class EventReviewController { @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0") }) public ApiResponse getEventReviewList(@PathVariable(name = "eventId") Long eventId, @RequestParam(name = "page") Integer page) { - return ApiResponse.onSuccess(EventReviewConverter.eventReviewPreViewListDTO(eventReviewCommandServivce.getEventReviews(eventId, page))); + return ApiResponse.onSuccess(EventReviewConverter.eventReviewPreViewListDTO(eventReviewCommandService.getEventReviews(eventId, page))); } } diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 58d2bc09..3c9f870f 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -4,7 +4,7 @@ import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -12,7 +12,7 @@ import java.util.Optional; public interface EventReviewRepository extends JpaRepository { - Page findAllByEvent(Event event, PageRequest pageRequest); + Page findAllByEvent(Event event, Pageable pageRequest); Optional findByRouteId(Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.route.id = :routeId") Optional findUserByRouteId(@Param("routeId") Long routeId); diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java similarity index 83% rename from src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java rename to src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java index 05963ae4..b0c3cbf1 100644 --- a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivce.java +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java @@ -3,6 +3,6 @@ import com.otakumap.domain.event_review.entity.EventReview; import org.springframework.data.domain.Page; -public interface EventReviewCommandServivce { +public interface EventReviewCommandService { Page getEventReviews(Long eventId, Integer page); } diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java similarity index 92% rename from src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java rename to src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java index be90dd8a..7151a0ac 100644 --- a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServivceImpl.java +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java @@ -13,7 +13,7 @@ @Service @RequiredArgsConstructor -public class EventReviewCommandServivceImpl implements EventReviewCommandServivce { +public class EventReviewCommandServiceImpl implements EventReviewCommandService { private final EventRepository eventRepository; private final EventReviewRepository eventReviewRepository; From 58786f7b1ec2e0147ae37b3fd72493cfec469c99 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 13:31:28 +0900 Subject: [PATCH 414/516] =?UTF-8?q?Refactor:=20Handler=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=9C=A0=EC=A0=80=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80,=20=EB=AA=85=EC=86=8C=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20api=EC=97=90=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EA=B0=80=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EB=B9=88=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventShortReviewRepository.java | 4 +-- .../PlaceShortReviewController.java | 4 +-- .../converter/PlaceShortReviewConverter.java | 33 ++++++++++++------- .../dto/PlaceShortReviewResponseDTO.java | 4 +-- .../PlaceShortReviewRepository.java | 7 ++-- .../PlaceShortReviewCommandService.java | 2 ++ .../PlaceShortReviewCommandServiceImpl.java | 11 ++++++- .../service/PlaceShortReviewQueryService.java | 8 ----- .../PlaceShortReviewQueryServiceImpl.java | 26 --------------- 9 files changed, 41 insertions(+), 58 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java delete mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java b/src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java index 21adf7ac..85543eb6 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_short_review/repository/EventShortReviewRepository.java @@ -3,9 +3,9 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_short_review.entity.EventShortReview; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface EventShortReviewRepository extends JpaRepository { - Page findAllByEvent(Event event, PageRequest pageRequest); + Page findAllByEvent(Event event, Pageable pageRequest); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 5761ec46..3ed5ef5a 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -3,7 +3,6 @@ import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.place_short_review.dto.PlaceShortReviewResponseDTO; import com.otakumap.domain.place_short_review.converter.PlaceShortReviewConverter; -import com.otakumap.domain.place_short_review.service.PlaceShortReviewQueryService; import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.service.PlaceShortReviewCommandService; @@ -27,7 +26,6 @@ public class PlaceShortReviewController { private final PlaceShortReviewCommandService placeShortReviewCommandService; - private final PlaceShortReviewQueryService placeShortReviewQueryService; @GetMapping("/places/{placeId}/short-review") @Operation(summary = "특정 명소의 한 줄 리뷰 목록 조회", description = "특정 명소의 한 줄 리뷰 목록을 불러옵니다.") @@ -39,7 +37,7 @@ public class PlaceShortReviewController { @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0") }) public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ - return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); + return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewCommandService.getPlaceShortReviews(placeId, page))); } @PostMapping("/places/{placeId}/short-review") diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index a7a1b2a9..fe6ab960 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -8,20 +8,25 @@ import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class PlaceShortReviewConverter { - public static PlaceShortReviewResponseDTO.PlaceShortReviewDTO placeShortReviewDTO(PlaceShortReview placeShortReview) { - PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO user = PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO.builder() - .id(placeShortReview.getUser().getId()) - .nickname(placeShortReview.getUser().getNickname()) - //.profilePicture(placeShortReview.getUser().getProfilePicture() + public static PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO placeShortReviewUserDTO(User user) { + return PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO.builder() + .userId(user.getUserId()) + .nickname(user.getNickname()) + .profileImage(user.getProfileImage().getFileUrl()) .build(); + } + + public static PlaceShortReviewResponseDTO.PlaceShortReviewDTO placeShortReviewDTO(PlaceShortReview placeShortReview) { + User user = placeShortReview.getUser(); return PlaceShortReviewResponseDTO.PlaceShortReviewDTO.builder() .id(placeShortReview.getId()) - .user(user) + .user(PlaceShortReviewConverter.placeShortReviewUserDTO(user)) .content(placeShortReview.getContent()) .rating(placeShortReview.getRating()) .createdAt(placeShortReview.getCreatedAt()) @@ -31,18 +36,22 @@ public static PlaceShortReviewResponseDTO.PlaceShortReviewDTO placeShortReviewDT } public static PlaceShortReviewResponseDTO.PlaceShortReviewListDTO placeShortReviewListDTO(Page reviewList) { - - if(reviewList == null || reviewList.isEmpty()) return null; + if(reviewList == null || reviewList.isEmpty()) { + return PlaceShortReviewResponseDTO.PlaceShortReviewListDTO.builder() + .shortReviews(new ArrayList<>()) + .totalPages(0) + .build(); + } List placeShortReviewDTOList = reviewList.stream() .map(PlaceShortReviewConverter::placeShortReviewDTO).collect(Collectors.toList()); - PlaceShortReview review = reviewList.getContent().get(0); + Place place = review.getPlace(); return PlaceShortReviewResponseDTO.PlaceShortReviewListDTO.builder() - .placeId(review.getPlace().getId()) - .placeName(review.getPlace().getName()) - .currentPage(reviewList.getNumber() + 1) + .placeId(place.getId()) + .placeName(place.getName()) + .currentPage(reviewList.getNumber()) .totalPages(reviewList.getTotalPages()) .shortReviews(placeShortReviewDTOList) .build(); diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java index f9809dac..158d080b 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -42,9 +42,9 @@ public static class PlaceShortReviewDTO { @NoArgsConstructor @AllArgsConstructor public static class PlaceShortReviewUserDTO { - Long id; + String userId; String nickname; - // ImageResponseDTO.ImageDTO profileImage; + String profileImage; } @Builder diff --git a/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java index 9bdf1859..75e2e605 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_short_review/repository/PlaceShortReviewRepository.java @@ -2,14 +2,13 @@ import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.domain.Page; import java.util.List; public interface PlaceShortReviewRepository extends JpaRepository { - Page findAllByPlace(Place place, PageRequest pageRequest); - + Page findAllByPlace(Place place, Pageable pageRequest); List findAllByPlace(Place place); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java index 947b9241..327486e2 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java @@ -3,9 +3,11 @@ import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Page; public interface PlaceShortReviewCommandService { PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); void updatePlaceShortReview(Long placeShortReviewId, PlaceShortReviewRequestDTO.UpdatePlaceShortReviewDTO request); void deletePlaceShortReview(Long placeShortReviewId); + Page getPlaceShortReviews(Long placeId, Integer page); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index 785ccc0d..27d7b3f7 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -13,13 +13,15 @@ import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor @Transactional public class PlaceShortReviewCommandServiceImpl implements PlaceShortReviewCommandService { - private final PlaceShortReviewRepository placeShortReviewRepository;; + private final PlaceShortReviewRepository placeShortReviewRepository; private final PlaceRepository placeRepository; private final PlaceAnimationRepository placeAnimationRepository; @@ -52,4 +54,11 @@ public void deletePlaceShortReview(Long placeShortReviewId) { placeShortReviewRepository.delete(placeShortReview); } + + + @Override + public Page getPlaceShortReviews(Long placeId, Integer page) { + Place place = placeRepository.findById(placeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + return placeShortReviewRepository.findAllByPlace(place, PageRequest.of(page, 6)); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java deleted file mode 100644 index d13157c7..00000000 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.otakumap.domain.place_short_review.service; - -import com.otakumap.domain.place_short_review.entity.PlaceShortReview; -import org.springframework.data.domain.Page; - -public interface PlaceShortReviewQueryService { - Page getPlaceShortReviews(Long placeId, Integer page); -} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java deleted file mode 100644 index 73879404..00000000 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.otakumap.domain.place_short_review.service; - -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_short_review.entity.PlaceShortReview; -import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.UserHandler; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class PlaceShortReviewQueryServiceImpl implements PlaceShortReviewQueryService { - - private final PlaceRepository placeRepository; - private final PlaceShortReviewRepository placeShortReviewRepository; - - @Override - public Page getPlaceShortReviews(Long placeId, Integer page) { - Place place = placeRepository.findById(placeId).orElseThrow(() -> new UserHandler(ErrorStatus.PLACE_NOT_FOUND)); - return placeShortReviewRepository.findAllByPlace(place, PageRequest.of(page, 6)); - } -} From c62b7c6ef69b0fe7afaf87f6b1c38caffa60c9f3 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 13:31:43 +0900 Subject: [PATCH 415/516] =?UTF-8?q?Refactor:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=20=EC=97=86=EB=8A=94=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/SecurityConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/otakumap/global/config/SecurityConfig.java b/src/main/java/com/otakumap/global/config/SecurityConfig.java index 26cb0d7c..4e7e87ab 100644 --- a/src/main/java/com/otakumap/global/config/SecurityConfig.java +++ b/src/main/java/com/otakumap/global/config/SecurityConfig.java @@ -40,6 +40,8 @@ public class SecurityConfig { private final String[] allowGetUrl = { "/api/events/**", "/api/reviews/**", + "/api/places/**", + "/api/routes/**", "/api/map/**", }; From 07ed72c6a94669f635752c6a84ebc59fba5ca912 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 13:32:10 +0900 Subject: [PATCH 416/516] =?UTF-8?q?Refactor:=20Review=20Converter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/converter/ReviewConverter.java | 28 +++++++++++++++---- .../repository/ReviewRepositoryImpl.java | 4 +-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 2e4c8262..d4fc3578 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -4,6 +4,8 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; +import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; @@ -24,12 +26,16 @@ public class ReviewConverter { public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO(EventReview eventReview) { + List images = eventReview.getImages(); + ImageResponseDTO.ImageDTO imageDTO = null; + if(images != null && !images.isEmpty()) { + imageDTO = ImageConverter.toImageDTO(images.get(0)); + } + return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(eventReview.getImages() != null && !eventReview.getImages().isEmpty() ? - ImageConverter.toImageDTO(eventReview.getImages().get(0)) : - null) // 나중에 수정 + .reviewImage(imageDTO) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("event") @@ -37,18 +43,28 @@ public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7EventReviewPreViewDTO } public static ReviewResponseDTO.Top7ReviewPreViewDTO toTop7PlaceReviewPreViewDTO(PlaceReview eventReview) { + List images = eventReview.getImages(); + ImageResponseDTO.ImageDTO imageDTO = null; + if(images != null && !images.isEmpty()) { + imageDTO = ImageConverter.toImageDTO(images.get(0)); + } + return ReviewResponseDTO.Top7ReviewPreViewDTO.builder() .id(eventReview.getId()) .title(eventReview.getTitle()) - .reviewImage(eventReview.getImages() != null && !eventReview.getImages().isEmpty() ? - ImageConverter.toImageDTO(eventReview.getImages().get(0)) : - null) // 나중에 수정 + .reviewImage(imageDTO) .view(eventReview.getView()) .createdAt(eventReview.getCreatedAt()) .type("place") .build(); } + public static ReviewResponseDTO.Top7ReviewPreViewListDTO top7ReviewPreViewListDTO(List reviews) { + return ReviewResponseDTO.Top7ReviewPreViewListDTO.builder() + .reviews(reviews) + .build(); + } + public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedEventReviewPreviewDTO(EventReview eventReview) { return ReviewResponseDTO.SearchedReviewPreViewDTO.builder() .reviewId(eventReview.getId()) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index e18013a9..b9fcc810 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -142,8 +142,6 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { .limit(7) .collect(Collectors.toList()); - return ReviewResponseDTO.Top7ReviewPreViewListDTO.builder() - .reviews(top7Reviews) - .build(); + return ReviewConverter.top7ReviewPreViewListDTO(top7Reviews); } } From 8a3fb21ab7a22fcca73564d312a6d40d07bb5ee7 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 13:32:40 +0900 Subject: [PATCH 417/516] =?UTF-8?q?Refactor:=20Event=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20api=EC=97=90=EC=84=9C=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=EB=90=9C=20event=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EC=9D=98=20=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_like/service/EventLikeCommandServiceImpl.java | 3 +++ .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 1 + 2 files changed, 4 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index bd85078d..c3242798 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -27,6 +27,9 @@ public class EventLikeCommandServiceImpl implements EventLikeCommandService { @Override public void addEventLike(User user, Long eventId) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + if(eventLikeRepository.existsByUserAndEvent(user, event)) { + throw new EventHandler(ErrorStatus.EVENT_LIKE_ALREADY_EXISTS); + } eventLikeRepository.save(EventLikeConverter.toEventLike(user, event)); } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 4b3700a4..71fe191d 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -45,6 +45,7 @@ public enum ErrorStatus implements BaseErrorCode { // 이벤트 좋아요 관련 에러 EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), + EVENT_LIKE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "EVENT4002", "이미 저장된 이벤트입니다."), // 이벤트 상세 정보 관련 에러 EVENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4002", "존재하지 않는 이벤트입니다."), From ccd839c4d4182385ec51c97643fd37e8af4081b7 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 13:34:13 +0900 Subject: [PATCH 418/516] =?UTF-8?q?Refactor:=20Review=20Controller=20respo?= =?UTF-8?q?nse=EB=A5=BC=20inline=ED=95=98=EC=97=AC=20=EB=8B=A8=EC=88=9C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/reviews/controller/ReviewController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index f084a679..302ccfd6 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -37,8 +37,7 @@ public class ReviewController { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), }) public ApiResponse getTop7ReviewList() { - ReviewResponseDTO.Top7ReviewPreViewListDTO top7Reviews = reviewQueryService.getTop7Reviews(); - return ApiResponse.onSuccess(top7Reviews); + return ApiResponse.onSuccess(reviewQueryService.getTop7Reviews()); } @GetMapping("/reviews/search") From b2d55c0bdb7fd901d9c485886adaa6a19c431e32 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 15:39:12 +0900 Subject: [PATCH 419/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20API=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_like/controller/EventLikeController.java | 6 +++--- .../event_like/repository/EventLikeRepository.java | 4 ++-- .../event_like/service/EventLikeCommandService.java | 2 +- .../service/EventLikeCommandServiceImpl.java | 11 +++++++++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java index c141b648..4e859876 100644 --- a/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java +++ b/src/main/java/com/otakumap/domain/event_like/controller/EventLikeController.java @@ -52,10 +52,10 @@ public ApiResponse getEventLikeLis @Operation(summary = "저장된 이벤트 삭제(이벤트 찜하기 취소)", description = "저장된 이벤트를 삭제합니다.") @DeleteMapping("") @Parameters({ - @Parameter(name = "eventIds", description = "저장된 이벤트 ID List"), + @Parameter(name = "eventIds", description = "이벤트 ID List"), }) - public ApiResponse deleteEventLike(@RequestParam(required = false) @ExistEventLike List eventIds) { - eventLikeCommandService.deleteEventLike(eventIds); + public ApiResponse deleteEventLike(@RequestParam(required = false) List eventIds, @CurrentUser User user) { + eventLikeCommandService.deleteEventLike(eventIds, user); return ApiResponse.onSuccess("저장된 이벤트가 성공적으로 삭제되었습니다"); } diff --git a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java index a07f21b1..77f46fdc 100644 --- a/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java +++ b/src/main/java/com/otakumap/domain/event_like/repository/EventLikeRepository.java @@ -5,9 +5,9 @@ import com.otakumap.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; -; - +import java.util.Optional; public interface EventLikeRepository extends JpaRepository { Boolean existsByUserAndEvent(User user, Event event); + Optional findByEventIdAndUserId(Long eventId, Long userId); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java index d22e1b6e..0d652632 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandService.java @@ -8,6 +8,6 @@ public interface EventLikeCommandService { void addEventLike(User user, Long eventId); - void deleteEventLike(List eventIds); + void deleteEventLike(List eventIds, User user); EventLike favoriteEventLike(Long eventLikeId, EventLikeRequestDTO.FavoriteDTO request); } diff --git a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java index bd85078d..dc0711b8 100644 --- a/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_like/service/EventLikeCommandServiceImpl.java @@ -31,8 +31,15 @@ public void addEventLike(User user, Long eventId) { } @Override - public void deleteEventLike(List eventIds) { - eventLikeRepository.deleteAllByIdInBatch(eventIds); + public void deleteEventLike(List eventIds, User user) { + List eventLikeIds = eventIds.stream() + .map(eventId -> eventLikeRepository.findByEventIdAndUserId(eventId, user.getId()) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_LIKE_NOT_FOUND)) + .getId() + ) + .toList(); + + eventLikeRepository.deleteAllByIdInBatch(eventLikeIds); entityManager.flush(); entityManager.clear(); } From b83e2cfdbfc2f2c7935e94aedbc8ed502c299b46 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 16:15:17 +0900 Subject: [PATCH 420/516] =?UTF-8?q?Feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20=EB=8B=89=EB=84=A4=EC=9E=84=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/auth/dto/KakaoUserInfo.java | 8 +++++++- .../com/otakumap/domain/user/converter/UserConverter.java | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java b/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java index 1e9f5551..c3482e6d 100644 --- a/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java +++ b/src/main/java/com/otakumap/domain/auth/dto/KakaoUserInfo.java @@ -8,7 +8,13 @@ public class KakaoUserInfo { @Getter public static class KakaoAccount { - private String name; + //private String name; + private Profile profile; private String email; } + + @Getter + public static class Profile { + private String nickname; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index a824249e..85d04b06 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -65,7 +65,7 @@ public static AuthResponseDTO.VerifyCodeResultDTO toVerifyCodeResultDTO(boolean public static User toKakaoUser(KakaoUserInfo kakaoUserInfo) { return User.builder() - .name(kakaoUserInfo.getKakao_account().getName()) + .name(kakaoUserInfo.getKakao_account().getProfile().getNickname()) .nickname(UuidGenerator.generateUuid()) .email(kakaoUserInfo.getKakao_account().getEmail()) .socialType(SocialType.KAKAO) From be2eb97fe959c6d225f1b127c702e97f8166350c Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:16:44 +0900 Subject: [PATCH 421/516] =?UTF-8?q?Feat:=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=ED=95=B4?= =?UTF-8?q?=EC=8B=9C=ED=83=9C=EA=B7=B8=EB=8F=84=20=EA=B0=99=EC=9D=B4=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AnimationCommandServiceImpl.java | 20 +++++++++++++++++-- .../hash_tag/converter/HashTagConverter.java | 16 +++++++++++++++ .../repository/HashTagRepository.java | 7 +++++++ .../AnimationHashTagRepository.java | 7 +++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/hash_tag/repository/HashTagRepository.java create mode 100644 src/main/java/com/otakumap/domain/mapping/repository/AnimationHashTagRepository.java diff --git a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java index bc6778a0..04152c92 100644 --- a/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/animation/service/AnimationCommandServiceImpl.java @@ -3,6 +3,10 @@ import com.otakumap.domain.animation.converter.AnimationConverter; import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.repository.AnimationRepository; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.hash_tag.repository.HashTagRepository; +import com.otakumap.domain.mapping.repository.AnimationHashTagRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.AnimationHandler; import jakarta.transaction.Transactional; @@ -13,6 +17,8 @@ @RequiredArgsConstructor public class AnimationCommandServiceImpl implements AnimationCommandService { private final AnimationRepository animationRepository; + private final HashTagRepository hashTagRepository; + private final AnimationHashTagRepository animationHashTagRepository; @Override @Transactional @@ -25,7 +31,7 @@ public Animation createAnimation(String name) { throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_LENGTH); } - if (!name.matches("^[가-힣a-zA-Z0-9\\\\s./()-]+$")) { + if (!name.matches("^[가-힣a-zA-Z0-9\\s./()!\\-]+$")) { throw new AnimationHandler(ErrorStatus.ANIMATION_NAME_SPECIAL_CHARACTER); } @@ -37,7 +43,17 @@ public Animation createAnimation(String name) { throw new AnimationHandler(ErrorStatus.ANIMATION_ALREADY_EXISTS); } + // 애니메이션 저장 Animation newAnimation = AnimationConverter.toAnimation(name); - return animationRepository.save(newAnimation); + animationRepository.save(newAnimation); + + // 해시태그 저장 -> #애니명 + HashTag hashTag = HashTagConverter.toHashTag(name); + hashTagRepository.save(hashTag); + + // 애니메이션-해시태그 매핑 테이블 저장 + animationHashTagRepository.save(HashTagConverter.toAnimationHashTag(newAnimation, hashTag)); + + return newAnimation; } } diff --git a/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java b/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java index 2df214e6..14834a4f 100644 --- a/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java +++ b/src/main/java/com/otakumap/domain/hash_tag/converter/HashTagConverter.java @@ -1,13 +1,29 @@ package com.otakumap.domain.hash_tag.converter; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.mapping.AnimationHashtag; public class HashTagConverter { + public static HashTag toHashTag(String name) { + return HashTag.builder() + .name("#" + name) + .build(); + + } + public static HashTagResponseDTO.HashTagDTO toHashTagDTO(HashTag hashTag) { return HashTagResponseDTO.HashTagDTO.builder() .hashTagId(hashTag.getId()) .name(hashTag.getName()) .build(); } + + public static AnimationHashtag toAnimationHashTag(Animation animation, HashTag hashTag) { + return AnimationHashtag.builder() + .animation(animation) + .hashTag(hashTag) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/hash_tag/repository/HashTagRepository.java b/src/main/java/com/otakumap/domain/hash_tag/repository/HashTagRepository.java new file mode 100644 index 00000000..6a1a7778 --- /dev/null +++ b/src/main/java/com/otakumap/domain/hash_tag/repository/HashTagRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.hash_tag.repository; + +import com.otakumap.domain.hash_tag.entity.HashTag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HashTagRepository extends JpaRepository { +} diff --git a/src/main/java/com/otakumap/domain/mapping/repository/AnimationHashTagRepository.java b/src/main/java/com/otakumap/domain/mapping/repository/AnimationHashTagRepository.java new file mode 100644 index 00000000..8a124092 --- /dev/null +++ b/src/main/java/com/otakumap/domain/mapping/repository/AnimationHashTagRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.mapping.repository; + +import com.otakumap.domain.mapping.AnimationHashtag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnimationHashTagRepository extends JpaRepository { +} From d18afa757dcf4e4157723b51f9ff7f4d4769d94a Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:27:45 +0900 Subject: [PATCH 422/516] =?UTF-8?q?Feat:=20Animation=20:=20HashTag=20=3D?= =?UTF-8?q?=20N:M=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/animation/entity/Animation.java | 16 ++++++++++- .../otakumap/domain/event/entity/Event.java | 5 ---- .../domain/hash_tag/entity/HashTag.java | 5 ++-- ...tionHashTag.java => AnimationHashtag.java} | 10 +++---- .../otakumap/domain/mapping/EventHashTag.java | 27 ------------------- .../domain/mapping/PlaceAnimation.java | 4 --- .../repository/EventHashTagRepository.java | 11 -------- 7 files changed, 22 insertions(+), 56 deletions(-) rename src/main/java/com/otakumap/domain/mapping/{PlaceAnimationHashTag.java => AnimationHashtag.java} (70%) delete mode 100644 src/main/java/com/otakumap/domain/mapping/EventHashTag.java delete mode 100644 src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java diff --git a/src/main/java/com/otakumap/domain/animation/entity/Animation.java b/src/main/java/com/otakumap/domain/animation/entity/Animation.java index cd853f0e..098477e1 100644 --- a/src/main/java/com/otakumap/domain/animation/entity/Animation.java +++ b/src/main/java/com/otakumap/domain/animation/entity/Animation.java @@ -1,20 +1,34 @@ package com.otakumap.domain.animation.entity; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.mapping.AnimationHashtag; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Animation extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 50) private String name; + + @OneToMany(mappedBy = "animation") + private List eventReviews = new ArrayList<>(); + + @OneToMany(mappedBy = "animation") + private List placeReviews = new ArrayList<>(); + + @OneToMany(mappedBy = "animation", cascade = CascadeType.ALL) + private List animationHashtags = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/event/entity/Event.java b/src/main/java/com/otakumap/domain/event/entity/Event.java index 1d7d6764..3137df0d 100644 --- a/src/main/java/com/otakumap/domain/event/entity/Event.java +++ b/src/main/java/com/otakumap/domain/event/entity/Event.java @@ -7,7 +7,6 @@ import com.otakumap.domain.event_like.entity.EventLike; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventAnimation; -import com.otakumap.domain.mapping.EventHashTag; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -23,7 +22,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Event extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -77,8 +75,5 @@ public class Event extends BaseEntity { @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) private List eventAnimationList = new ArrayList<>(); - @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) - private List eventHashTagList = new ArrayList<>(); - public void startEvent() { this.status = EventStatus.IN_PROCESS; } } diff --git a/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java b/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java index 865fa316..943cab5c 100644 --- a/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java +++ b/src/main/java/com/otakumap/domain/hash_tag/entity/HashTag.java @@ -1,6 +1,6 @@ package com.otakumap.domain.hash_tag.entity; -import com.otakumap.domain.mapping.PlaceAnimationHashTag; +import com.otakumap.domain.mapping.AnimationHashtag; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -14,7 +14,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class HashTag extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -23,5 +22,5 @@ public class HashTag extends BaseEntity { private String name; @OneToMany(mappedBy = "hashTag", cascade = CascadeType.ALL) - private List placeAnimationHashTags = new ArrayList<>(); + private List animationHashtags = new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java b/src/main/java/com/otakumap/domain/mapping/AnimationHashtag.java similarity index 70% rename from src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java rename to src/main/java/com/otakumap/domain/mapping/AnimationHashtag.java index 7c843935..2b55b1d7 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceAnimationHashTag.java +++ b/src/main/java/com/otakumap/domain/mapping/AnimationHashtag.java @@ -1,7 +1,7 @@ package com.otakumap.domain.mapping; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.hash_tag.entity.HashTag; -import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -10,16 +10,16 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class PlaceAnimationHashTag extends BaseEntity { +public class AnimationHashtag { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_animation_id") - private PlaceAnimation placeAnimation; + @JoinColumn(name = "animation_id") + private Animation animation; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "hash_tag_id") private HashTag hashTag; -} +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/mapping/EventHashTag.java b/src/main/java/com/otakumap/domain/mapping/EventHashTag.java deleted file mode 100644 index 2c895d0e..00000000 --- a/src/main/java/com/otakumap/domain/mapping/EventHashTag.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.otakumap.domain.mapping; - -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.hash_tag.entity.HashTag; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class EventHashTag extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "event_id") - private Event event; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "hash_tag_id") - private HashTag hashTag; -} diff --git a/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java index f2b4b6ce..3d6227e0 100644 --- a/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java +++ b/src/main/java/com/otakumap/domain/mapping/PlaceAnimation.java @@ -16,7 +16,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class PlaceAnimation extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -29,9 +28,6 @@ public class PlaceAnimation extends BaseEntity { @JoinColumn(name = "animation_id") private Animation animation; - @OneToMany(mappedBy = "placeAnimation", cascade = CascadeType.ALL) - private List placeAnimationHashTags = new ArrayList<>(); - @OneToMany(mappedBy = "placeAnimation", cascade = CascadeType.ALL) private List placeLikes = new ArrayList<>(); } diff --git a/src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java b/src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java deleted file mode 100644 index 83ce91fe..00000000 --- a/src/main/java/com/otakumap/domain/mapping/repository/EventHashTagRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.otakumap.domain.mapping.repository; - -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.mapping.EventHashTag; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface EventHashTagRepository extends JpaRepository { - List findByEvent(Event event); -} From 88adfde35d0979bc1fff9c942e1720c3f7232222 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:29:44 +0900 Subject: [PATCH 423/516] =?UTF-8?q?Chore:=20=ED=95=B4=EC=8B=9C=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20API=20=EC=A3=BC=EC=84=9D=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/map/controller/MapController.java | 48 +-- .../repository/MapRepositoryCustomImpl.java | 152 ++++----- .../map/service/MapCustomServiceImpl.java | 24 +- .../controller/PlaceReviewController.java | 70 ++-- .../service/PlaceReviewQueryServiceImpl.java | 222 ++++++------- .../search/controller/SearchController.java | 40 +-- .../search/service/SearchServiceImpl.java | 312 +++++++++--------- 7 files changed, 434 insertions(+), 434 deletions(-) diff --git a/src/main/java/com/otakumap/domain/map/controller/MapController.java b/src/main/java/com/otakumap/domain/map/controller/MapController.java index 0362ccbc..fe6808d8 100644 --- a/src/main/java/com/otakumap/domain/map/controller/MapController.java +++ b/src/main/java/com/otakumap/domain/map/controller/MapController.java @@ -13,27 +13,27 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -@RestController -@RequestMapping("/api/map") -@RequiredArgsConstructor -@Validated -public class MapController { - - private final MapCustomService mapCustomService; - - @GetMapping("/details") - @Operation(summary = "지도에서 장소 및 이벤트 정보 보기", description = "장소의 Latitude, Longitude 수령해 해당 장소의 명소와 이벤트를 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), - }) - @Parameters({ - @Parameter(name = "latitude"), - @Parameter(name = "longitude"), - }) - public ApiResponse getSearchedPlaceInfoList( - @CurrentUser User user, - @RequestParam Double latitude, - @RequestParam Double longitude) { - return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(user, latitude, longitude)); - } -} +//@RestController +//@RequestMapping("/api/map") +//@RequiredArgsConstructor +//@Validated +//public class MapController { +// +// private final MapCustomService mapCustomService; +// +// @GetMapping("/details") +// @Operation(summary = "지도에서 장소 및 이벤트 정보 보기", description = "장소의 Latitude, Longitude 수령해 해당 장소의 명소와 이벤트를 조회합니다.") +// @ApiResponses({ +// @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), +// }) +// @Parameters({ +// @Parameter(name = "latitude"), +// @Parameter(name = "longitude"), +// }) +// public ApiResponse getSearchedPlaceInfoList( +// @CurrentUser User user, +// @RequestParam Double latitude, +// @RequestParam Double longitude) { +// return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(user, latitude, longitude)); +// } +//} diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java index 2c35c7c8..0c9ddd5d 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java @@ -1,76 +1,76 @@ -package com.otakumap.domain.map.repository; - -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.event.repository.EventRepository; -import com.otakumap.domain.event_like.repository.EventLikeRepository; -import com.otakumap.domain.map.converter.MapConverter; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.map.dto.MapResponseDTO; -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.EventAnimation; -import com.otakumap.domain.hash_tag.entity.HashTag; -import com.otakumap.domain.mapping.EventHashTag; -import com.otakumap.domain.place_like.repository.PlaceLikeRepository; -import com.otakumap.domain.user.entity.User; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; - -@Repository -@RequiredArgsConstructor -public class MapRepositoryCustomImpl implements MapRepositoryCustom { - - private final EventRepository eventRepository; - private final PlaceRepository placeRepository; - private final EventLikeRepository eventLikeRepository; - private final PlaceLikeRepository placeLikeRepository; - - @Override - public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { - List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); - List eventDTOs = eventList.stream().map(event -> { - Boolean isLiked = Boolean.FALSE; - List eventHashTags = event.getEventHashTagList().stream() - .map(EventHashTag::getHashTag) - .collect(Collectors.toList()); - - List eventAnimations = event.getEventAnimationList().stream() - .map(EventAnimation::getAnimation) - .toList(); - if(user!=null) { - isLiked = eventLikeRepository.existsByUserAndEvent(user, event); - } - return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); - }).collect(Collectors.toList()); - - List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); - List placeDTOs = placeList.stream().map(place -> { - List placeHashTags = new ArrayList<>(); - AtomicReference isLiked = new AtomicReference<>(Boolean.FALSE); - - place.getPlaceAnimationList().forEach(placeAnimation -> { - placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { - placeHashTags.add(hashTag.getHashTag()); - }); - - if(user!=null) { - isLiked.set(placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation)); - } - }); - - List placeAnimations = place.getPlaceAnimationList().stream() - .map(PlaceAnimation::getAnimation) - .toList(); - - return MapConverter.toMapDetailPlaceDTO(place, isLiked.get(), placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); - }).collect(Collectors.toList()); - - return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); - } -} +//package com.otakumap.domain.map.repository; +// +//import com.otakumap.domain.event.entity.Event; +//import com.otakumap.domain.event.repository.EventRepository; +//import com.otakumap.domain.event_like.repository.EventLikeRepository; +//import com.otakumap.domain.map.converter.MapConverter; +//import com.otakumap.domain.place.entity.Place; +//import com.otakumap.domain.place.repository.PlaceRepository; +//import com.otakumap.domain.map.dto.MapResponseDTO; +//import com.otakumap.domain.animation.entity.Animation; +//import com.otakumap.domain.mapping.PlaceAnimation; +//import com.otakumap.domain.mapping.EventAnimation; +//import com.otakumap.domain.hash_tag.entity.HashTag; +//import com.otakumap.domain.mapping.EventHashTag; +//import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +//import com.otakumap.domain.user.entity.User; +//import lombok.RequiredArgsConstructor; +//import org.springframework.stereotype.Repository; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.concurrent.atomic.AtomicReference; +//import java.util.stream.Collectors; +// +//@Repository +//@RequiredArgsConstructor +//public class MapRepositoryCustomImpl implements MapRepositoryCustom { +// +// private final EventRepository eventRepository; +// private final PlaceRepository placeRepository; +// private final EventLikeRepository eventLikeRepository; +// private final PlaceLikeRepository placeLikeRepository; +// +// @Override +// public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { +// List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); +// List eventDTOs = eventList.stream().map(event -> { +// Boolean isLiked = Boolean.FALSE; +// List eventHashTags = event.getEventHashTagList().stream() +// .map(EventHashTag::getHashTag) +// .collect(Collectors.toList()); +// +// List eventAnimations = event.getEventAnimationList().stream() +// .map(EventAnimation::getAnimation) +// .toList(); +// if(user!=null) { +// isLiked = eventLikeRepository.existsByUserAndEvent(user, event); +// } +// return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); +// }).collect(Collectors.toList()); +// +// List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); +// List placeDTOs = placeList.stream().map(place -> { +// List placeHashTags = new ArrayList<>(); +// AtomicReference isLiked = new AtomicReference<>(Boolean.FALSE); +// +// place.getPlaceAnimationList().forEach(placeAnimation -> { +// placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { +// placeHashTags.add(hashTag.getHashTag()); +// }); +// +// if(user!=null) { +// isLiked.set(placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation)); +// } +// }); +// +// List placeAnimations = place.getPlaceAnimationList().stream() +// .map(PlaceAnimation::getAnimation) +// .toList(); +// +// return MapConverter.toMapDetailPlaceDTO(place, isLiked.get(), placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); +// }).collect(Collectors.toList()); +// +// return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); +// } +//} diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java index b140742b..148e3cd1 100644 --- a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java @@ -7,15 +7,15 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class MapCustomServiceImpl implements MapCustomService { - - private final MapRepositoryCustom mapRepositoryCustom; - - @Override - public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { - return mapRepositoryCustom.findAllMapDetails(user, latitude, longitude); - } -} \ No newline at end of file +//@Service +//@RequiredArgsConstructor +//@Transactional(readOnly = true) +//public class MapCustomServiceImpl implements MapCustomService { +// +// private final MapRepositoryCustom mapRepositoryCustom; +// +// @Override +// public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { +// return mapRepositoryCustom.findAllMapDetails(user, latitude, longitude); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index 8d0a3680..9b12e777 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -1,35 +1,35 @@ -package com.otakumap.domain.place_review.controller; - -import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; -import com.otakumap.domain.place_review.service.PlaceReviewQueryService; -import com.otakumap.global.apiPayload.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api") -public class PlaceReviewController { - private final PlaceReviewQueryService placeReviewQueryService; - - @GetMapping("/places/{placeId}/reviews") - @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") - @Parameters({ - @Parameter(name = "placeId", description = "특정 장소의 id 입니다."), - @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), - @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), - @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") - }) - public ApiResponse getPlaceReviewList(@PathVariable Long placeId, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "latest") String sort) { - - PlaceReviewResponseDTO.PlaceAnimationReviewDTO results = placeReviewQueryService.getReviewsByPlace(placeId, page, size, sort); - - return ApiResponse.onSuccess(results); - } -} +//package com.otakumap.domain.place_review.controller; +// +//import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +//import com.otakumap.domain.place_review.service.PlaceReviewQueryService; +//import com.otakumap.global.apiPayload.ApiResponse; +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.Parameter; +//import io.swagger.v3.oas.annotations.Parameters; +//import lombok.RequiredArgsConstructor; +//import org.springframework.web.bind.annotation.*; +// +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("/api") +//public class PlaceReviewController { +// private final PlaceReviewQueryService placeReviewQueryService; +// +// @GetMapping("/places/{placeId}/reviews") +// @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") +// @Parameters({ +// @Parameter(name = "placeId", description = "특정 장소의 id 입니다."), +// @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), +// @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), +// @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") +// }) +// public ApiResponse getPlaceReviewList(@PathVariable Long placeId, +// @RequestParam(defaultValue = "0") int page, +// @RequestParam(defaultValue = "10") int size, +// @RequestParam(defaultValue = "latest") String sort) { +// +// PlaceReviewResponseDTO.PlaceAnimationReviewDTO results = placeReviewQueryService.getReviewsByPlace(placeId, page, size, sort); +// +// return ApiResponse.onSuccess(results); +// } +//} diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 75590500..d84da32c 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -1,111 +1,111 @@ -package com.otakumap.domain.place_review.service; - -import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.hash_tag.converter.HashTagConverter; -import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; -import com.otakumap.domain.mapping.PlaceAnimation; -import com.otakumap.domain.mapping.PlaceReviewPlace; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; -import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; -import com.otakumap.domain.place_short_review.entity.PlaceShortReview; -import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { - - private final PlaceRepository placeRepository; - private final PlaceReviewRepository placeReviewRepository; - private final PlaceShortReviewRepository placeShortReviewRepository; - private final PlaceReviewPlaceRepository placeReviewPlaceRepository; - - @Override - public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { - - Place place = placeRepository.findById(placeId) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - - // 전체 한 줄 리뷰 평균 별점 구하기 - List placeShortReviewList = placeShortReviewRepository.findAllByPlace(place); - double avgRating = placeShortReviewList.stream() - .mapToDouble(PlaceShortReview::getRating) - .average() - .orElse(0.0); - Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); - - // 전체 리뷰 리스트 - List placeReviewPlaces = placeReviewPlaceRepository.findByPlace(place); - List allReviews = placeReviewPlaces.stream() - .map(prp -> placeReviewRepository.findById(prp.getPlaceReview().getId())) - .flatMap(Optional::stream) - .toList(); - - // 애니메이션별 해시태그 - List placeAnimations = allReviews.stream() - .map(PlaceReview::getPlaceAnimation) - .filter(Objects::nonNull) - .distinct() - .toList(); - Map> animationHashTagMap = - placeAnimations.stream() - .collect(Collectors.groupingBy( - PlaceAnimation::getAnimation, // 그룹의 키 : PlaceAnimation이 참조하는 Animation - - Collectors.flatMapping( - pa -> pa.getPlaceAnimationHashTags().stream() - .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())), - Collectors.toList() - ) - )); - - // 애니메이션별로 리뷰 그룹화 - Map> reviewsByAnimation = allReviews.stream() - .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); - - // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 - List animationGroups = paginateReviews(reviewsByAnimation, animationHashTagMap, page, size); - - // 총 리뷰 수 계산 - long totalReviews = reviewsByAnimation.values().stream() - .mapToLong(List::size) - .sum(); - - return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); - } - - private List paginateReviews(Map> reviewsByAnimation, - Map> animationHashTagMap, - int page, int size) { - - return reviewsByAnimation.entrySet().stream() - .map(entry -> { - Animation animation = entry.getKey(); - List reviews = entry.getValue(); - // 각 애니메이션에 해당하는 해시태그 목록을 가져오기 - List hashTagsForAnimation = animationHashTagMap.getOrDefault(animation, Collections.emptyList()); - - int fromIndex = Math.min(page * size, reviews.size()); - int toIndex = Math.min(fromIndex + size, reviews.size()); - - List pagedReviews = reviews.subList(fromIndex, toIndex); - - return PlaceReviewConverter.toAnimationReviewGroupDTO(animation, pagedReviews, hashTagsForAnimation); - }) - .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 - .toList(); - } -} +//package com.otakumap.domain.place_review.service; +// +//import com.otakumap.domain.animation.entity.Animation; +//import com.otakumap.domain.hash_tag.converter.HashTagConverter; +//import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +//import com.otakumap.domain.mapping.PlaceAnimation; +//import com.otakumap.domain.mapping.PlaceReviewPlace; +//import com.otakumap.domain.place.entity.Place; +//import com.otakumap.domain.place.repository.PlaceRepository; +//import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +//import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +//import com.otakumap.domain.place_review.entity.PlaceReview; +//import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +//import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; +//import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +//import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +//import com.otakumap.global.apiPayload.code.status.ErrorStatus; +//import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +//import lombok.RequiredArgsConstructor; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//import java.util.*; +//import java.util.stream.Collectors; +// +//@Service +//@RequiredArgsConstructor +//@Transactional(readOnly = true) +//public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { +// +// private final PlaceRepository placeRepository; +// private final PlaceReviewRepository placeReviewRepository; +// private final PlaceShortReviewRepository placeShortReviewRepository; +// private final PlaceReviewPlaceRepository placeReviewPlaceRepository; +// +// @Override +// public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { +// +// Place place = placeRepository.findById(placeId) +// .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); +// +// // 전체 한 줄 리뷰 평균 별점 구하기 +// List placeShortReviewList = placeShortReviewRepository.findAllByPlace(place); +// double avgRating = placeShortReviewList.stream() +// .mapToDouble(PlaceShortReview::getRating) +// .average() +// .orElse(0.0); +// Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); +// +// // 전체 리뷰 리스트 +// List placeReviewPlaces = placeReviewPlaceRepository.findByPlace(place); +// List allReviews = placeReviewPlaces.stream() +// .map(prp -> placeReviewRepository.findById(prp.getPlaceReview().getId())) +// .flatMap(Optional::stream) +// .toList(); +// +// // 애니메이션별 해시태그 +// List placeAnimations = allReviews.stream() +// .map(PlaceReview::getPlaceAnimation) +// .filter(Objects::nonNull) +// .distinct() +// .toList(); +// Map> animationHashTagMap = +// placeAnimations.stream() +// .collect(Collectors.groupingBy( +// PlaceAnimation::getAnimation, // 그룹의 키 : PlaceAnimation이 참조하는 Animation +// +// Collectors.flatMapping( +// pa -> pa.getPlaceAnimationHashTags().stream() +// .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())), +// Collectors.toList() +// ) +// )); +// +// // 애니메이션별로 리뷰 그룹화 +// Map> reviewsByAnimation = allReviews.stream() +// .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); +// +// // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 +// List animationGroups = paginateReviews(reviewsByAnimation, animationHashTagMap, page, size); +// +// // 총 리뷰 수 계산 +// long totalReviews = reviewsByAnimation.values().stream() +// .mapToLong(List::size) +// .sum(); +// +// return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); +// } +// +// private List paginateReviews(Map> reviewsByAnimation, +// Map> animationHashTagMap, +// int page, int size) { +// +// return reviewsByAnimation.entrySet().stream() +// .map(entry -> { +// Animation animation = entry.getKey(); +// List reviews = entry.getValue(); +// // 각 애니메이션에 해당하는 해시태그 목록을 가져오기 +// List hashTagsForAnimation = animationHashTagMap.getOrDefault(animation, Collections.emptyList()); +// +// int fromIndex = Math.min(page * size, reviews.size()); +// int toIndex = Math.min(fromIndex + size, reviews.size()); +// +// List pagedReviews = reviews.subList(fromIndex, toIndex); +// +// return PlaceReviewConverter.toAnimationReviewGroupDTO(animation, pagedReviews, hashTagsForAnimation); +// }) +// .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 +// .toList(); +// } +//} diff --git a/src/main/java/com/otakumap/domain/search/controller/SearchController.java b/src/main/java/com/otakumap/domain/search/controller/SearchController.java index 5eb2c0ad..9c348a69 100644 --- a/src/main/java/com/otakumap/domain/search/controller/SearchController.java +++ b/src/main/java/com/otakumap/domain/search/controller/SearchController.java @@ -17,23 +17,23 @@ import java.util.List; -@RestController -@RequiredArgsConstructor -@RequestMapping("/api") -public class SearchController { - - private final SearchService searchService; - - @GetMapping("/map/search") - @Operation(summary = "이벤트/작품명 지도 검색", description = "키워드로 이벤트/장소명/애니메이션 제목을 검색하여 관련된 장소 위치와 그 위치의 이벤트를 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), - }) - @Parameters({ - @Parameter(name = "keyword", description = "검색 키워드입니다."), - }) - public ApiResponse> getSearchedPlaceInfoList(@CurrentUser(required=false) User user, @RequestParam String keyword) { - - return ApiResponse.onSuccess(searchService.getSearchedResult(user, keyword)); - } -} +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("/api") +//public class SearchController { +// +// private final SearchService searchService; +// +// @GetMapping("/map/search") +// @Operation(summary = "이벤트/작품명 지도 검색", description = "키워드로 이벤트/장소명/애니메이션 제목을 검색하여 관련된 장소 위치와 그 위치의 이벤트를 조회합니다.") +// @ApiResponses({ +// @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), +// }) +// @Parameters({ +// @Parameter(name = "keyword", description = "검색 키워드입니다."), +// }) +// public ApiResponse> getSearchedPlaceInfoList(@CurrentUser(required=false) User user, @RequestParam String keyword) { +// +// return ApiResponse.onSuccess(searchService.getSearchedResult(user, keyword)); +// } +//} diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index f1c58c7a..e9259c59 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -1,156 +1,156 @@ -package com.otakumap.domain.search.service; - -import com.otakumap.domain.animation.converter.AnimationConverter; -import com.otakumap.domain.animation.dto.AnimationResponseDTO; -import com.otakumap.domain.event.converter.EventConverter; -import com.otakumap.domain.event.dto.EventResponseDTO; -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.event.entity.enums.EventStatus; -import com.otakumap.domain.event_like.repository.EventLikeRepository; -import com.otakumap.domain.event_location.entity.EventLocation; -import com.otakumap.domain.event_location.repository.EventLocationRepository; -import com.otakumap.domain.hash_tag.converter.HashTagConverter; -import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; -import com.otakumap.domain.mapping.EventHashTag; -import com.otakumap.domain.mapping.repository.EventHashTagRepository; -import com.otakumap.domain.place.DTO.PlaceResponseDTO; -import com.otakumap.domain.place.converter.PlaceConverter; -import com.otakumap.domain.place.entity.Place; -import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_like.repository.PlaceLikeRepository; -import com.otakumap.domain.search.converter.SearchConverter; -import com.otakumap.domain.search.dto.SearchResponseDTO; -import com.otakumap.domain.search.repository.SearchRepositoryCustom; -import com.otakumap.domain.user.entity.User; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@Service -@RequiredArgsConstructor -public class SearchServiceImpl implements SearchService { - - private final SearchRepositoryCustom searchRepository; - private final EventLocationRepository eventLocationRepository; - private final EventLikeRepository eventLikeRepository; - private final EventHashTagRepository eventHashTagRepository; - private final PlaceLikeRepository placeLikeRepository; - private final PlaceRepository placeRepository; - - @Transactional(readOnly = true) - @Override - public List getSearchedResult (User user, String keyword) { - - List events = searchRepository.searchEventsByKeyword(keyword); - List places = searchRepository.searchPlacesByKeyword(keyword); - - List safePlaces = places != null ? places : Collections.emptyList(); // 불변 - List safeEvents = events != null ? events : new ArrayList<>(); // 가변 - - // 검색한 결과에 장소가 있을 때 -> 각 장소의 위/경도와 같은 곳에서 진행되고 있는 event를 찾음 - List newEvents = safePlaces.stream() - .flatMap(place -> eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()).stream()) - .map(EventLocation::getEvent) - .filter(event -> event != null && event.getStatus() != EventStatus.ENDED) - .toList(); - - // 기존에 검색된 이벤트와 합치기 - List combinedEvents = - Stream.concat(safeEvents.stream(), newEvents.stream()) - .distinct() // 중복 이벤트 제거 - .toList(); - - // 이벤트를 위치(위도, 경도) 기준으로 그룹화 - Map> groupedEvents = combinedEvents.stream() - .filter(event -> event.getEventLocation() != null) - .collect(Collectors.groupingBy(event -> - event.getEventLocation().getLat() + "," + event.getEventLocation().getLng() - )); - - // 장소들을 위치 기준으로 그룹화 (같은 위치에 여러 명소가 있을 수 있으므로 그룹화) - Map> groupedPlaces = safePlaces.stream() - .collect(Collectors.groupingBy(place -> place.getLat() + "," + place.getLng())); - - // 만약 groupedEvents에는 존재하는 위치(key)가 groupedPlaces에 없다면, - // 해당 위치의 장소들을 조회해서 groupedPlaces에 추가 - for (String key : groupedEvents.keySet()) { - if (!groupedPlaces.containsKey(key)) { - String[] parts = key.split(","); - Double lat = Double.valueOf(parts[0]); - Double lng = Double.valueOf(parts[1]); - - List additionalPlaces = placeRepository.findByLatAndLng(lat, lng); - - if (additionalPlaces != null && !additionalPlaces.isEmpty()) { - groupedPlaces.put(key, additionalPlaces); - } - } - } - - // 모든 위치 키의 합집합 - Set allKeys = new HashSet<>(); - allKeys.addAll(groupedEvents.keySet()); - allKeys.addAll(groupedPlaces.keySet()); - - // 각 위치 키별로 이벤트와 장소 정보를 DTO로 변환하여 SearchResultDTO 생성 - return allKeys.stream().map(key -> { - String[] parts = key.split(","); - Double lat = Double.valueOf(parts[0]); - Double lng = Double.valueOf(parts[1]); - - // 해당 위치의 이벤트들을 DTO로 변환 - List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) - .stream().map(event -> { - boolean isLiked = false; - if(user != null) { - isLiked = eventLikeRepository.existsByUserAndEvent(user, event); - } - - // 이벤트에 연결된 해시태그 조회 - List eventHashTags = eventHashTagRepository.findByEvent(event); - List hashTags = eventHashTags.stream() - .map(eht -> HashTagConverter.toHashTagDTO(eht.getHashTag())) - .collect(Collectors.toList()); - - // eventAnimationList를 이용해 애니메이션 제목을 추출 (여러 개가 있을 경우 콤마로 연결) - String animationTitle = event.getEventAnimationList().stream() - .map(ea -> ea.getAnimation().getName()) - .collect(Collectors.joining(", ")); - - return EventConverter.toSearchedEventInfoDTO(event, isLiked, animationTitle, hashTags); - }) - .collect(Collectors.toList()); - - // 장소 DTO 변환 - List placeDTOs = groupedPlaces.getOrDefault(key, Collections.emptyList()) - .stream().map(place -> { - // 각 장소의 모든 PlaceAnimation을 AnimationInfoDTO 리스트로 변환 (애니메이션별 좋아요 여부 계산) - List animationDTOs = place.getPlaceAnimationList().stream() - .map(placeAnimation -> { - boolean isLiked = false; - // 각 장소의 애니메이션별 좋아요 여부 - if (user != null) { - isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); - } - - List hashTags = placeAnimation.getPlaceAnimationHashTags().stream() - .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())) - .collect(Collectors.toList()); - - return AnimationConverter.toAnimationInfoDTO(placeAnimation, isLiked, hashTags); - }) - .collect(Collectors.toList()); - - return PlaceConverter.toSearchedPlaceInfoDTO(place, animationDTOs); - }) - .toList(); - - return SearchConverter.toSearchResultDTO(eventDTOs, placeDTOs, lat, lng); - }).collect(Collectors.toList()); - } - -} +//package com.otakumap.domain.search.service; +// +//import com.otakumap.domain.animation.converter.AnimationConverter; +//import com.otakumap.domain.animation.dto.AnimationResponseDTO; +//import com.otakumap.domain.event.converter.EventConverter; +//import com.otakumap.domain.event.dto.EventResponseDTO; +//import com.otakumap.domain.event.entity.Event; +//import com.otakumap.domain.event.entity.enums.EventStatus; +//import com.otakumap.domain.event_like.repository.EventLikeRepository; +//import com.otakumap.domain.event_location.entity.EventLocation; +//import com.otakumap.domain.event_location.repository.EventLocationRepository; +//import com.otakumap.domain.hash_tag.converter.HashTagConverter; +//import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +//import com.otakumap.domain.mapping.EventHashTag; +//import com.otakumap.domain.mapping.repository.EventHashTagRepository; +//import com.otakumap.domain.place.DTO.PlaceResponseDTO; +//import com.otakumap.domain.place.converter.PlaceConverter; +//import com.otakumap.domain.place.entity.Place; +//import com.otakumap.domain.place.repository.PlaceRepository; +//import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +//import com.otakumap.domain.search.converter.SearchConverter; +//import com.otakumap.domain.search.dto.SearchResponseDTO; +//import com.otakumap.domain.search.repository.SearchRepositoryCustom; +//import com.otakumap.domain.user.entity.User; +//import lombok.RequiredArgsConstructor; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//import java.util.*; +//import java.util.stream.Collectors; +//import java.util.stream.Stream; +// +//@Service +//@RequiredArgsConstructor +//public class SearchServiceImpl implements SearchService { +// +// private final SearchRepositoryCustom searchRepository; +// private final EventLocationRepository eventLocationRepository; +// private final EventLikeRepository eventLikeRepository; +// private final EventHashTagRepository eventHashTagRepository; +// private final PlaceLikeRepository placeLikeRepository; +// private final PlaceRepository placeRepository; +// +// @Transactional(readOnly = true) +// @Override +// public List getSearchedResult (User user, String keyword) { +// +// List events = searchRepository.searchEventsByKeyword(keyword); +// List places = searchRepository.searchPlacesByKeyword(keyword); +// +// List safePlaces = places != null ? places : Collections.emptyList(); // 불변 +// List safeEvents = events != null ? events : new ArrayList<>(); // 가변 +// +// // 검색한 결과에 장소가 있을 때 -> 각 장소의 위/경도와 같은 곳에서 진행되고 있는 event를 찾음 +// List newEvents = safePlaces.stream() +// .flatMap(place -> eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()).stream()) +// .map(EventLocation::getEvent) +// .filter(event -> event != null && event.getStatus() != EventStatus.ENDED) +// .toList(); +// +// // 기존에 검색된 이벤트와 합치기 +// List combinedEvents = +// Stream.concat(safeEvents.stream(), newEvents.stream()) +// .distinct() // 중복 이벤트 제거 +// .toList(); +// +// // 이벤트를 위치(위도, 경도) 기준으로 그룹화 +// Map> groupedEvents = combinedEvents.stream() +// .filter(event -> event.getEventLocation() != null) +// .collect(Collectors.groupingBy(event -> +// event.getEventLocation().getLat() + "," + event.getEventLocation().getLng() +// )); +// +// // 장소들을 위치 기준으로 그룹화 (같은 위치에 여러 명소가 있을 수 있으므로 그룹화) +// Map> groupedPlaces = safePlaces.stream() +// .collect(Collectors.groupingBy(place -> place.getLat() + "," + place.getLng())); +// +// // 만약 groupedEvents에는 존재하는 위치(key)가 groupedPlaces에 없다면, +// // 해당 위치의 장소들을 조회해서 groupedPlaces에 추가 +// for (String key : groupedEvents.keySet()) { +// if (!groupedPlaces.containsKey(key)) { +// String[] parts = key.split(","); +// Double lat = Double.valueOf(parts[0]); +// Double lng = Double.valueOf(parts[1]); +// +// List additionalPlaces = placeRepository.findByLatAndLng(lat, lng); +// +// if (additionalPlaces != null && !additionalPlaces.isEmpty()) { +// groupedPlaces.put(key, additionalPlaces); +// } +// } +// } +// +// // 모든 위치 키의 합집합 +// Set allKeys = new HashSet<>(); +// allKeys.addAll(groupedEvents.keySet()); +// allKeys.addAll(groupedPlaces.keySet()); +// +// // 각 위치 키별로 이벤트와 장소 정보를 DTO로 변환하여 SearchResultDTO 생성 +// return allKeys.stream().map(key -> { +// String[] parts = key.split(","); +// Double lat = Double.valueOf(parts[0]); +// Double lng = Double.valueOf(parts[1]); +// +// // 해당 위치의 이벤트들을 DTO로 변환 +// List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) +// .stream().map(event -> { +// boolean isLiked = false; +// if(user != null) { +// isLiked = eventLikeRepository.existsByUserAndEvent(user, event); +// } +// +// // 이벤트에 연결된 해시태그 조회 +// List eventHashTags = eventHashTagRepository.findByEvent(event); +// List hashTags = eventHashTags.stream() +// .map(eht -> HashTagConverter.toHashTagDTO(eht.getHashTag())) +// .collect(Collectors.toList()); +// +// // eventAnimationList를 이용해 애니메이션 제목을 추출 (여러 개가 있을 경우 콤마로 연결) +// String animationTitle = event.getEventAnimationList().stream() +// .map(ea -> ea.getAnimation().getName()) +// .collect(Collectors.joining(", ")); +// +// return EventConverter.toSearchedEventInfoDTO(event, isLiked, animationTitle, hashTags); +// }) +// .collect(Collectors.toList()); +// +// // 장소 DTO 변환 +// List placeDTOs = groupedPlaces.getOrDefault(key, Collections.emptyList()) +// .stream().map(place -> { +// // 각 장소의 모든 PlaceAnimation을 AnimationInfoDTO 리스트로 변환 (애니메이션별 좋아요 여부 계산) +// List animationDTOs = place.getPlaceAnimationList().stream() +// .map(placeAnimation -> { +// boolean isLiked = false; +// // 각 장소의 애니메이션별 좋아요 여부 +// if (user != null) { +// isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); +// } +// +// List hashTags = placeAnimation.getPlaceAnimationHashTags().stream() +// .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())) +// .collect(Collectors.toList()); +// +// return AnimationConverter.toAnimationInfoDTO(placeAnimation, isLiked, hashTags); +// }) +// .collect(Collectors.toList()); +// +// return PlaceConverter.toSearchedPlaceInfoDTO(place, animationDTOs); +// }) +// .toList(); +// +// return SearchConverter.toSearchResultDTO(eventDTOs, placeDTOs, lat, lng); +// }).collect(Collectors.toList()); +// } +// +//} From c379d88ce361064fa5a127f72f325ee15e7b060a Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:36:56 +0900 Subject: [PATCH 424/516] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=98=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/converter/ReviewConverter.java | 20 +---- .../service/ReviewCommandServiceImpl.java | 82 +++++-------------- .../route/converter/RouteConverter.java | 6 ++ .../converter/RouteItemConverter.java | 9 ++ 4 files changed, 41 insertions(+), 76 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 2e4c8262..c7c37bf6 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -1,10 +1,8 @@ package com.otakumap.domain.reviews.converter; import com.otakumap.domain.animation.entity.Animation; -import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; @@ -77,7 +75,7 @@ public static ReviewResponseDTO.SearchedReviewPreViewDTO toSearchedPlaceReviewPr public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceReview placeReview) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(placeReview.getId()) - .animationName(placeReview.getPlaceAnimation().getAnimation().getName() != null ? placeReview.getPlaceAnimation().getAnimation().getName() : null) + .animationName(placeReview.getAnimation().getName() != null ? placeReview.getAnimation().getName() : null) .title(placeReview.getTitle()) .view(placeReview.getView()) .content(placeReview.getContent()) @@ -95,7 +93,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventReview eventReview) { return ReviewResponseDTO.ReviewDetailDTO.builder() .reviewId(eventReview.getId()) - .animationName(eventReview.getEventAnimation().getAnimation().getName() != null ? eventReview.getEventAnimation().getAnimation().getName() : null) + .animationName(eventReview.getAnimation().getName() != null ? eventReview.getAnimation().getName() : null) .title(eventReview.getTitle()) .view(eventReview.getView()) .content(eventReview.getContent()) @@ -118,25 +116,22 @@ public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewI .build(); } - public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, List eventReviewPlaces, Route route) { + public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, Route route) { return EventReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) - .placeList(eventReviewPlaces) .route(route) - .rating(0F) .build(); } - public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, List placeReviewPlaces, Route route) { + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Route route) { return PlaceReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) - .placeList(placeReviewPlaces) .route(route) .build(); } @@ -175,11 +170,4 @@ public static PlaceAnimation toPlaceAnimation(Place place, Animation animation) .animation(animation) .build(); } - - public static EventAnimation toEventAnimation(Event event, Animation animation) { - return EventAnimation.builder() - .event(event) - .animation(animation) - .build(); - } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 5520a596..6d40ed84 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -2,16 +2,11 @@ import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.animation.repository.AnimationRepository; -import com.otakumap.domain.event.entity.Event; -import com.otakumap.domain.event_animation.repository.EventAnimationRepository; -import com.otakumap.domain.event_location.entity.EventLocation; -import com.otakumap.domain.event_location.repository.EventLocationRepository; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.event_review_place.repository.EventReviewPlaceRepository; import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.mapping.EventReviewPlace; -import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; @@ -23,6 +18,7 @@ import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.route_item.converter.RouteItemConverter; @@ -38,14 +34,12 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ReviewCommandServiceImpl implements ReviewCommandService { - private final PlaceReviewRepository placeReviewRepository; private final EventReviewRepository eventReviewRepository; private final AnimationRepository animationRepository; @@ -53,9 +47,7 @@ public class ReviewCommandServiceImpl implements ReviewCommandService { private final RouteRepository routeRepository; private final RouteItemRepository routeItemRepository; private final ImageCommandService imageCommandService; - private final EventLocationRepository eventLocationRepository; private final PlaceAnimationRepository placeAnimationRepository; - private final EventAnimationRepository eventAnimationRepository; private final PlaceReviewPlaceRepository placeReviewPlaceRepository; private final EventReviewPlaceRepository eventReviewPlaceRepository; @@ -65,7 +57,7 @@ public ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDT Animation animation = animationRepository.findById(request.getAnimeId()) .orElseThrow(() -> new AnimationHandler(ErrorStatus.ANIMATION_NOT_FOUND)); - Route route = saveRoute(request.getTitle()); + Route route = routeRepository.save(RouteConverter.toRoute(request.getTitle())); List routeItems = createRouteItems(request.getRouteItems(), animation, route); routeItemRepository.saveAll(routeItems); @@ -76,49 +68,24 @@ public ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDT private List createRouteItems(List routeDTOs, Animation animation, Route route) { return routeDTOs.stream() .map(routeDTO -> { - Place place = findOrSavePlace(routeDTO); - RouteItem routeItem = RouteItemConverter.toRouteItem(routeDTO.getOrder(), place); - routeItem.setRoute(route); - associateAnimationWithPlaceOrEvent(place, animation, getItemType(place)); - return routeItem; + // 위도, 경도에 해당하는 Place가 있으면 찾고 없으면 새로 생성해서 반환 + Place place = findOrCreatePlace(routeDTO); + // place에 대한 placeAnimation 생성 + createPlaceAnimation(place, animation); + // place, route에 대한 루트 아이템 생성 + return RouteItemConverter.toRouteItem(routeDTO.getOrder(), route, place); }) .collect(Collectors.toList()); } - private Place findOrSavePlace(ReviewRequestDTO.RouteDTO routeDTO) { + private Place findOrCreatePlace(ReviewRequestDTO.RouteDTO routeDTO) { return placeRepository.findByNameAndLatAndLng(routeDTO.getName(), routeDTO.getLat(), routeDTO.getLng()) .orElseGet(() -> placeRepository.save(ReviewConverter.toPlace(routeDTO))); } - // 장소인지 이벤트인지 확인 - private ReviewType getItemType(Place place) { - return eventLocationRepository.existsByLatAndLng(place.getLat(), place.getLng()) ? ReviewType.EVENT : ReviewType.PLACE; - } - - // 장소 또는 이벤트에 애니메이션을 연결 - private PlaceAnimation associateAnimationWithPlaceOrEvent(Place place, Animation animation, ReviewType itemType) { - if (itemType == ReviewType.PLACE) { - return placeAnimationRepository.findByPlaceIdAndAnimationId(place.getId(), animation.getId()) - .orElseGet(() -> placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation))); // placeAnimation 반환 - } else { - // Place에 해당하는 Event 찾기 - List eventLocations = eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()); - EventLocation eventLocation = eventLocations.stream().findFirst() - .orElseThrow(() -> new ReviewHandler(ErrorStatus.EVENT_NOT_FOUND)); - Event event = eventLocation.getEvent(); - - // Event 객체를 이용해 EventAnimation 생성 및 저장 - eventAnimationRepository.save(ReviewConverter.toEventAnimation(event, animation)); - - return null; // Event는 따로 ID 반환할 필요 없음 - } - } - - private Route saveRoute(String title) { - return routeRepository.save(Route.builder() - .name(title) - .routeItems(new ArrayList<>()) - .build()); + private void createPlaceAnimation(Place place, Animation animation) { + placeAnimationRepository.findByPlaceIdAndAnimationId(place.getId(), animation.getId()) + .orElseGet(() -> placeAnimationRepository.save(ReviewConverter.toPlaceAnimation(place, animation))); // placeAnimation 반환 } // 리뷰 저장 및 반환 @@ -132,42 +99,37 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO if (request.getReviewType() == ReviewType.PLACE) { // 먼저 PlaceReview를 저장 - PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, new ArrayList<>(), route); - placeReview = placeReviewRepository.save(placeReview); - - PlaceAnimation placeAnimation = associateAnimationWithPlaceOrEvent(places.get(0), animation, ReviewType.PLACE); - placeReview.setPlaceAnimation(placeAnimation); + PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, route); + placeReview.setAnimation(animation); placeReview = placeReviewRepository.save(placeReview); + // placeReviewPlace를 저장(placeReview에 해당하는 routeItem의 place 저장) List placeReviewPlaces = ReviewConverter.toPlaceReviewPlaceList(places, placeReview); - placeReviewPlaceRepository.saveAll(placeReviewPlaces); + placeReviewPlaceRepository.saveAll(ReviewConverter.toPlaceReviewPlaceList(places, placeReview)); - // placeList 업데이트 후 다시 저장 + // placeList 업데이트 placeReview.setPlaceList(placeReviewPlaces); - placeReview = placeReviewRepository.save(placeReview); imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ReviewType.PLACE); - return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); + return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); } else if (request.getReviewType() == ReviewType.EVENT) { // 먼저 EventReview를 저장 - EventReview eventReview = ReviewConverter.toEventReview(request, user, new ArrayList<>(), route); + EventReview eventReview = ReviewConverter.toEventReview(request, user, route); + eventReview.setAnimation(animation); eventReview = eventReviewRepository.save(eventReview); - // 저장된 EventReview를 기반으로 eventReviewPlaces 생성 + // eventReviewPlaces 저장(eventReview에 해당하는 routeItem의 place 저장) List eventReviewPlaces = ReviewConverter.toEventReviewPlaceList(places, eventReview); eventReviewPlaceRepository.saveAll(eventReviewPlaces); - // placeList 업데이트 후 다시 저장 + // placeList 업데이트 eventReview.setPlaceList(eventReviewPlaces); - eventReview = eventReviewRepository.save(eventReview); imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ReviewType.EVENT); return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); - } else { throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } - } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java index ccbea18e..2cb96e86 100644 --- a/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java +++ b/src/main/java/com/otakumap/domain/route/converter/RouteConverter.java @@ -33,6 +33,12 @@ public static Route toRoute(String name, List routeItems) { return route; } + public static Route toRoute(String name) { + return Route.builder() + .name(name) + .build(); + } + public static RouteResponseDTO.RouteDetailDTO toRouteDetailDTO(Route route, Animation animation, List placeDTOs) { return new RouteResponseDTO.RouteDetailDTO( route.getId(), diff --git a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java index 56db07bb..39bf80a6 100644 --- a/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java +++ b/src/main/java/com/otakumap/domain/route_item/converter/RouteItemConverter.java @@ -1,6 +1,7 @@ package com.otakumap.domain.route_item.converter; import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_item.dto.RouteItemResponseDTO; import com.otakumap.domain.route_item.entity.RouteItem; @@ -12,6 +13,14 @@ public static RouteItem toRouteItem(Integer itemOrder, Place place) { .build(); } + public static RouteItem toRouteItem(Integer itemOrder, Route route, Place place) { + return RouteItem.builder() + .itemOrder(itemOrder) + .route(route) + .place(place) + .build(); + } + public static RouteItemResponseDTO.RouteItemDTO toRouteItemDTO(RouteItem routeItem) { return RouteItemResponseDTO.RouteItemDTO.builder() .routeItemId(routeItem.getId()) From 446367238fd71897b426db3b7dbee8dfd6f4f310 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:38:06 +0900 Subject: [PATCH 425/516] =?UTF-8?q?Feat:=20Animation=20:=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8/=EC=9E=A5=EC=86=8C=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=3D=201=20:=20N=20=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_review/entity/EventReview.java | 15 +++++---------- .../domain/place/converter/PlaceConverter.java | 2 +- .../place_like/converter/PlaceLikeConverter.java | 6 +++--- .../domain/place_review/entity/PlaceReview.java | 9 ++++----- .../route/service/RouteQueryServiceImpl.java | 8 ++++---- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index c93da32d..c35464ea 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -1,8 +1,8 @@ package com.otakumap.domain.event_review.entity; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; @@ -36,13 +36,6 @@ public class EventReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; - @Column(nullable = false) - private Float rating; - -// @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) -// @JoinColumn(name = "image_id", referencedColumnName = "id") -// private Image image; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") private List images = new ArrayList<>(); @@ -58,12 +51,14 @@ public class EventReview extends BaseEntity { private Event event; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "event_animation_id") - private EventAnimation eventAnimation; + @JoinColumn(name = "animation_id") + private Animation animation; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; public void setPlaceList(List placeList) { this.placeList = placeList; } + + public void setAnimation(Animation animation) { this.animation = animation; } } diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index 692202a4..ce6fd518 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -44,7 +44,7 @@ public static PlaceResponseDTO.PlaceDetailDTO toPlaceDetailDTO(Place place, Plac } public static List toPlaceHashtagsDTO(PlaceAnimation placeAnimation) { - return placeAnimation.getPlaceAnimationHashTags() + return placeAnimation.getAnimation().getAnimationHashtags() .stream() .map(placeAnimationHashTag -> HashTagConverter.toHashTagDTO(placeAnimationHashTag.getHashTag())).toList(); } diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index 200d95fb..d5045e22 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -55,9 +55,9 @@ public static PlaceLikeResponseDTO.PlaceLikeDetailDTO placeLikeDetailDTO(PlaceLi .lng(place.getLng()) .isLiked(Boolean.TRUE) // 저장한 장소를 조회하는 거니까 항상 true // 장소-애니메이션에 대한 해시태그 - .hashtags(placeLike.getPlaceAnimation().getPlaceAnimationHashTags() - .stream() - .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())).toList()) + .hashtags(placeLike.getPlaceAnimation().getAnimation().getAnimationHashtags().stream() + .map(placeHashTag -> HashTagConverter.toHashTagDTO(placeHashTag.getHashTag())) + .toList()) .build(); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 93f088d5..315cf99c 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -1,9 +1,8 @@ package com.otakumap.domain.place_review.entity; +import com.otakumap.domain.animation.entity.Animation; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; -import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; @@ -47,8 +46,8 @@ public class PlaceReview extends BaseEntity { private User user; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "place_animation_id") - private PlaceAnimation placeAnimation; + @JoinColumn(name = "animation_id") + private Animation animation; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "route_id", referencedColumnName = "id") @@ -56,5 +55,5 @@ public class PlaceReview extends BaseEntity { public void setPlaceList(List placeList) { this.placeList = placeList; } - public void setPlaceAnimation(PlaceAnimation placeAnimation) { this.placeAnimation = placeAnimation; } + public void setAnimation(Animation animation) { this.animation = animation; } } diff --git a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java index 2ac19bf0..f9cec81e 100644 --- a/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route/service/RouteQueryServiceImpl.java @@ -54,11 +54,11 @@ public RouteResponseDTO.RouteDetailDTO getRouteDetail(User user, Long routeId) { Animation animation = null; if (placeReviewOpt.isPresent()) { - animation = placeReviewOpt.get().getPlaceAnimation() != null ? - placeReviewOpt.get().getPlaceAnimation().getAnimation() : null; + animation = placeReviewOpt.get().getAnimation() != null ? + placeReviewOpt.get().getAnimation() : null; } else { - animation = eventReviewOpt.get().getEventAnimation() != null ? - eventReviewOpt.get().getEventAnimation().getAnimation() : null; + animation = eventReviewOpt.get().getAnimation() != null ? + eventReviewOpt.get().getAnimation() : null; } // 관련된 애니메이션이 없으면 예외 발생 From bfab44949d156564a1771bc2260095a850c34098 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:39:11 +0900 Subject: [PATCH 426/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EB=82=B4?= =?UTF-8?q?=20=ED=8A=B9=EC=A0=95=20=EC=9E=A5=EC=86=8C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API=EC=97=90?= =?UTF-8?q?=EC=84=9C=20place=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=B0=A9=EB=B2=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/place/service/PlaceQueryServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java index 84f8ba31..8e2f8630 100644 --- a/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place/service/PlaceQueryServiceImpl.java @@ -8,6 +8,7 @@ import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; import com.otakumap.domain.place_like.repository.PlaceLikeRepository; import com.otakumap.domain.route.repository.RouteRepository; +import com.otakumap.domain.route_item.repository.RouteItemRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; @@ -26,6 +27,7 @@ public class PlaceQueryServiceImpl implements PlaceQueryService { private final PlaceAnimationRepository placeAnimationRepository; private final PlaceLikeRepository placeLikeRepository; private final RouteRepository routeRepository; + private final RouteItemRepository routeItemRepository; @Override public boolean isPlaceExist(Long placeId) { @@ -48,7 +50,7 @@ public PlaceResponseDTO.PlaceDetailDTO getPlaceDetail(User user, Long routeId, L } // Place 조회 - Place place = placeRepository.findById(placeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + Place place = routeItemRepository.findPlaceByRouteIdAndPlaceId(routeId, placeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); // 애니메이션 관련 정보 조회 PlaceAnimation placeAnimation = placeAnimationRepository.findByPlaceIdAndAnimationId(placeId, animationId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); From 4148c552b7301d6b9e9a4357d9e38e2a71b6aa6d Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 17:39:39 +0900 Subject: [PATCH 427/516] =?UTF-8?q?Chore:=20=EC=95=88=20=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_location/repository/EventLocationRepository.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java index 7536aeb3..8cd90bf1 100644 --- a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java +++ b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java @@ -6,6 +6,4 @@ import java.util.List; public interface EventLocationRepository extends JpaRepository { - boolean existsByLatAndLng(Double latitude, Double longitude); - List findByLatAndLng(Double latitude, Double longitude); } \ No newline at end of file From 0e1c85f3684a9baf562b4b06bd180ec21b1dba40 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 16 Feb 2025 18:18:19 +0900 Subject: [PATCH 428/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=95=84?= =?UTF-8?q?=EC=88=98=20X=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/controller/ReviewController.java | 3 +-- .../domain/reviews/service/ReviewCommandServiceImpl.java | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index f084a679..80c8c1ed 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -27,7 +27,6 @@ @RequestMapping("/api") @Validated public class ReviewController { - private final ReviewQueryService reviewQueryService; private final ReviewCommandService reviewCommandService; @@ -80,7 +79,7 @@ public ApiResponse getReviewDetail(@PathVaria @Operation(summary = "여행 후기 작성", description = "여행 후기를 작성합니다. 장소, 이벤트 후기 중 하나만 작성할 수 있으며, 최소 1개 이상의 루트 아이템이 필요합니다.") public ApiResponse createReview(@Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) @RequestPart("request") @Valid ReviewRequestDTO.CreateDTO request, - @CurrentUser User user, @RequestPart("review images") MultipartFile[] images) { + @CurrentUser User user, @RequestPart(value = "review images", required = false) MultipartFile[] images) { ReviewResponseDTO.CreatedReviewDTO createdReview = reviewCommandService.createReview(request, user, images); return ApiResponse.onSuccess(createdReview); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 6d40ed84..9cbd37e2 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -110,7 +110,9 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO // placeList 업데이트 placeReview.setPlaceList(placeReviewPlaces); - imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ReviewType.PLACE); + if(images != null) { + imageCommandService.uploadReviewImages(List.of(images), placeReview.getId(), ReviewType.PLACE); + } return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); } else if (request.getReviewType() == ReviewType.EVENT) { @@ -126,7 +128,9 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO // placeList 업데이트 eventReview.setPlaceList(eventReviewPlaces); - imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ReviewType.EVENT); + if(images != null) { + imageCommandService.uploadReviewImages(List.of(images), eventReview.getId(), ReviewType.EVENT); + } return ReviewConverter.toCreatedReviewDTO(eventReview.getId(), eventReview.getTitle()); } else { throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); From 507ade7e0f14b931535ded02a10b9a7138316ca4 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Sun, 16 Feb 2025 21:27:33 +0900 Subject: [PATCH 429/516] =?UTF-8?q?Refactor:=20Service=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlaceShortReviewController.java | 5 ++-- .../PlaceShortReviewCommandService.java | 1 - .../PlaceShortReviewCommandServiceImpl.java | 9 ------- .../service/PlaceShortReviewQueryService.java | 8 ++++++ .../PlaceShortReviewQueryServiceImpl.java | 27 +++++++++++++++++++ 5 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java create mode 100644 src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java index 3ed5ef5a..43bd4936 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java +++ b/src/main/java/com/otakumap/domain/place_short_review/controller/PlaceShortReviewController.java @@ -6,6 +6,7 @@ import com.otakumap.domain.place_short_review.dto.PlaceShortReviewRequestDTO; import com.otakumap.domain.place_short_review.entity.PlaceShortReview; import com.otakumap.domain.place_short_review.service.PlaceShortReviewCommandService; +import com.otakumap.domain.place_short_review.service.PlaceShortReviewQueryService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import com.otakumap.global.validation.annotation.ExistPlace; @@ -24,7 +25,7 @@ @RequestMapping("/api") public class PlaceShortReviewController { - + private final PlaceShortReviewQueryService placeShortReviewQueryService; private final PlaceShortReviewCommandService placeShortReviewCommandService; @GetMapping("/places/{placeId}/short-review") @@ -37,7 +38,7 @@ public class PlaceShortReviewController { @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0") }) public ApiResponse getPlaceShortReviewList(@ExistPlace @PathVariable(name = "placeId") Long placeId, @RequestParam(name = "page") Integer page){ - return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewCommandService.getPlaceShortReviews(placeId, page))); + return ApiResponse.onSuccess(PlaceShortReviewConverter.placeShortReviewListDTO(placeShortReviewQueryService.getPlaceShortReviews(placeId, page))); } @PostMapping("/places/{placeId}/short-review") diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java index 327486e2..a17662d8 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandService.java @@ -9,5 +9,4 @@ public interface PlaceShortReviewCommandService { PlaceShortReview createReview(User user, Long placeId, PlaceShortReviewRequestDTO.CreateDTO request); void updatePlaceShortReview(Long placeShortReviewId, PlaceShortReviewRequestDTO.UpdatePlaceShortReviewDTO request); void deletePlaceShortReview(Long placeShortReviewId); - Page getPlaceShortReviews(Long placeId, Integer page); } diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java index 27d7b3f7..992ad172 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewCommandServiceImpl.java @@ -13,8 +13,6 @@ import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @@ -54,11 +52,4 @@ public void deletePlaceShortReview(Long placeShortReviewId) { placeShortReviewRepository.delete(placeShortReview); } - - - @Override - public Page getPlaceShortReviews(Long placeId, Integer page) { - Place place = placeRepository.findById(placeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); - return placeShortReviewRepository.findAllByPlace(place, PageRequest.of(page, 6)); - } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java new file mode 100644 index 00000000..d13157c7 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.place_short_review.service; + +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import org.springframework.data.domain.Page; + +public interface PlaceShortReviewQueryService { + Page getPlaceShortReviews(Long placeId, Integer page); +} diff --git a/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java new file mode 100644 index 00000000..adbb1d02 --- /dev/null +++ b/src/main/java/com/otakumap/domain/place_short_review/service/PlaceShortReviewQueryServiceImpl.java @@ -0,0 +1,27 @@ +package com.otakumap.domain.place_short_review.service; + + +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PlaceShortReviewQueryServiceImpl implements PlaceShortReviewQueryService { + private final PlaceShortReviewRepository placeShortReviewRepository; + private final PlaceRepository placeRepository; + + + @Override + public Page getPlaceShortReviews(Long placeId, Integer page) { + Place place = placeRepository.findById(placeId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + return placeShortReviewRepository.findAllByPlace(place, PageRequest.of(page, 6)); + } +} From 194e3f30ab03920e1c37d306a28ec4ee5984b55c Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 17 Feb 2025 01:44:36 +0900 Subject: [PATCH 430/516] =?UTF-8?q?Feature:=20=EA=B2=B0=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EA=B3=BC=20=EA=B4=80=EB=A0=A8=EB=90=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20entity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 10 +++- .../domain/payment/entity/Payment.java | 45 ++++++++++++++++ .../domain/payment/enums/PaymentStatus.java | 5 ++ .../otakumap/domain/point/entity/Point.java | 53 +++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/payment/entity/Payment.java create mode 100644 src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java create mode 100644 src/main/java/com/otakumap/domain/point/entity/Point.java diff --git a/build.gradle b/build.gradle index ebcf7408..7e7b039b 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ configurations { repositories { mavenCentral() + maven { url 'https://jitpack.io' } } jar{ @@ -67,12 +68,19 @@ dependencies { implementation 'com.google.code.gson:gson' // 구글 places api 관련 -// implementation 'com.google.maps:google-maps-services:0.42.2' + // implementation 'com.google.maps:google-maps-services:0.42.2' // Jackson: JSON 응답을 파싱 implementation 'com.fasterxml.jackson.core:jackson-databind' // AWS S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // iamport 결제 관련 + implementation group: 'com.github.iamport', name: 'iamport-rest-client-java', version: '0.2.22' + implementation group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.9.0' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3' + implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.3.0' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/payment/entity/Payment.java b/src/main/java/com/otakumap/domain/payment/entity/Payment.java new file mode 100644 index 00000000..22e24b66 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/entity/Payment.java @@ -0,0 +1,45 @@ +package com.otakumap.domain.payment.entity; + +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "payment") +public class Payment extends BaseEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "imp_uid", nullable = false) + private String impUid; + + @Column(name = "merchant_uid", nullable = false) + private String merchantUid; + + @Column(nullable = false) + private Long amount; + + @Column(name = "verified_at") + private LocalDateTime verifiedAt; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PaymentStatus status = PaymentStatus.PENDING; +} diff --git a/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java b/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java new file mode 100644 index 00000000..37466826 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.payment.enums; + +public enum PaymentStatus { + PENDING, PAID, FAILED +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java new file mode 100644 index 00000000..c72acca5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -0,0 +1,53 @@ +package com.otakumap.domain.point.entity; + +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "point") +public class Point extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false) + private Long point; + + @Column(name = "charged_at", nullable = false, updatable = false) + @CreationTimestamp + private LocalDateTime chargedAt; + + @Column(name = "charged_by") + private String chargedBy; + + @Column(name = "point_after_charge") + private Long pointAfterCharge; + + @Column(name = "merchant_uid", unique = true, nullable = false) + private String merchantUid; + + @Column(name = "imp_uid") + private String impUid; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PaymentStatus status = PaymentStatus.PENDING; +} From e2af2c3e82bb27a6663a5bddfa167e838d5bd306 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Mon, 17 Feb 2025 02:46:29 +0900 Subject: [PATCH 431/516] =?UTF-8?q?Feat:=20=EC=A7=84=ED=96=89=20=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20api=EC=97=90=20isLike?= =?UTF-8?q?d=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/controller/EventController.java | 6 ++- .../event/converter/EventConverter.java | 12 +++++ .../domain/event/dto/EventResponseDTO.java | 13 +++++ .../repository/EventRepositoryCustom.java | 3 +- .../event/repository/EventRepositoryImpl.java | 47 ++++++++----------- .../event/service/EventCustomService.java | 3 +- .../event/service/EventCustomServiceImpl.java | 5 +- 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 15cf5cfb..8d5541ae 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -1,10 +1,12 @@ package com.otakumap.domain.event.controller; +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.event.converter.EventConverter; import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.service.EventCustomService; import com.otakumap.domain.event.service.EventQueryService; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -25,8 +27,8 @@ public class EventController { @Operation(summary = "진행 중인 인기 이벤트 조회", description = "진행 중인 인기 이벤트의 목록(8개)를 불러옵니다.") @GetMapping("/events/popular") - public ApiResponse> getEventDetail() { - return ApiResponse.onSuccess(eventCustomService.getPopularEvents()); + public ApiResponse> getEventDetail(@CurrentUser User user) { + return ApiResponse.onSuccess(eventCustomService.getPopularEvents(user)); } @Operation(summary = "이벤트 상세 정보 조회", description = "특정 이벤트의 상세 정보를 불러옵니다.") diff --git a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java index b3af9f6b..49ba4fdd 100644 --- a/src/main/java/com/otakumap/domain/event/converter/EventConverter.java +++ b/src/main/java/com/otakumap/domain/event/converter/EventConverter.java @@ -56,4 +56,16 @@ public static EventResponseDTO.SearchedEventInfoDTO toSearchedEventInfoDTO(Event .hashTags(hashTags) .build(); } + + public static EventResponseDTO.EventWithLikeDTO toEventWithLikeDTO(Event event, Boolean isLiked) { + return EventResponseDTO.EventWithLikeDTO.builder() + .id(event.getId()) + .title(event.getTitle()) + .isLiked(isLiked) + .startDate(event.getStartDate()) + .endDate(event.getEndDate()) + .thumbnail(ImageConverter.toImageDTO(event.getThumbnailImage())) + .build(); + + } } diff --git a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java index e0991ed9..d4ad3685 100644 --- a/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event/dto/EventResponseDTO.java @@ -24,6 +24,19 @@ public static class EventDTO { ImageResponseDTO.ImageDTO thumbnail; } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventWithLikeDTO { + Long id; + String title; + Boolean isLiked; + LocalDate startDate; + LocalDate endDate; + ImageResponseDTO.ImageDTO thumbnail; + } + @Builder @Getter @NoArgsConstructor diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java index f8c2ff59..a4c3e0bc 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryCustom.java @@ -2,12 +2,13 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import java.util.List; public interface EventRepositoryCustom { - List getPopularEvents(); + List getPopularEvents(User user); ImageResponseDTO.ImageDTO getEventBanner(); Page getEventByGenre(String genre, Integer page, Integer size); Page getEventByStatusAndType(String status, String type, Integer page, Integer size); diff --git a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java index 6f6d18c2..915bc74d 100644 --- a/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/event/repository/EventRepositoryImpl.java @@ -6,8 +6,10 @@ import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event.entity.enums.EventType; import com.otakumap.domain.event.entity.enums.Genre; +import com.otakumap.domain.event_like.repository.EventLikeRepository; import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.querydsl.core.BooleanBuilder; @@ -17,6 +19,8 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; @@ -26,63 +30,52 @@ @Repository @RequiredArgsConstructor +@Transactional(readOnly = true) public class EventRepositoryImpl implements EventRepositoryCustom { private final JPAQueryFactory queryFactory; + private final EventLikeRepository eventLikeRepository; @Override - public List getPopularEvents() { + public List getPopularEvents(User user) { QEvent event = QEvent.event; - List eventIds = queryFactory.select(event.id) - .from(event) + List events = queryFactory.selectFrom(event) .where(event.endDate.goe(LocalDate.now()) .and(event.startDate.loe(LocalDate.now()))) .fetch(); - List randomIds; - if(eventIds.size() <= 8) { - randomIds = eventIds; - } else { - Collections.shuffle(eventIds); - randomIds = eventIds.subList(0, 8); + if(events.size() > 8) { + Collections.shuffle(events); + events = events.subList(0, 8); } - List events = queryFactory.selectFrom(event) - .where(event.id.in(randomIds)) - .fetch(); - return events.stream() - .map(EventConverter::toEventDTO) - .collect(Collectors.toList()); + .map(eve -> { + Boolean isLiked = (user != null) ? eventLikeRepository.existsByUserAndEvent(user, eve) : false; + return EventConverter.toEventWithLikeDTO(eve, isLiked); + }).collect(Collectors.toList()); } @Override public ImageResponseDTO.ImageDTO getEventBanner() { QEvent event = QEvent.event; - List targetEventIds = queryFactory.select(event.id) - .from(event) + List targetEvents = queryFactory.selectFrom(event) .where(event.endDate.goe(LocalDate.now()) .and(event.startDate.loe(LocalDate.now())) .and(event.thumbnailImage.isNotNull())) .fetch(); - Long targetEventId; - if(targetEventIds.isEmpty()) { + if(targetEvents.isEmpty()) { return ImageResponseDTO.ImageDTO.builder() .fileUrl("default_banner_url") .build(); - } else if(targetEventIds.size() > 1) { - Collections.shuffle(targetEventIds); + } else if(targetEvents.size() > 1) { + Collections.shuffle(targetEvents); } - targetEventId = targetEventIds.get(0); - - Event targetEvent = queryFactory.selectFrom(event) - .where(event.id.eq(targetEventId)) - .fetchOne(); - return ImageConverter.toImageDTO((targetEvent) + return ImageConverter.toImageDTO((targetEvents.get(0)) .getThumbnailImage()); } diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java index 38134c93..dc3cb92b 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventCustomService.java +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomService.java @@ -2,12 +2,13 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import java.util.List; public interface EventCustomService { - List getPopularEvents(); + List getPopularEvents(User user); ImageResponseDTO.ImageDTO getEventBanner(); Page searchEventByCategory(String genre, String status, String type, Integer page, Integer size); } diff --git a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java index 6f146b46..5adb82ea 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event/service/EventCustomServiceImpl.java @@ -3,6 +3,7 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.repository.EventRepositoryCustom; import com.otakumap.domain.image.dto.ImageResponseDTO; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import lombok.RequiredArgsConstructor; @@ -18,8 +19,8 @@ public class EventCustomServiceImpl implements EventCustomService { private final EventRepositoryCustom eventRepository; @Override - public List getPopularEvents() { - return eventRepository.getPopularEvents(); + public List getPopularEvents(User user) { + return eventRepository.getPopularEvents(user); } @Override From f171d1b76275d8fcf306bb140373de808860b795 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 17 Feb 2025 04:18:20 +0900 Subject: [PATCH 432/516] =?UTF-8?q?Feat:=20=ED=8A=B9=EC=A0=95=20=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=20=EC=A0=84=EC=B2=B4=20=ED=9B=84=EA=B8=B0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=EC=9D=98=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=ED=95=B4=EC=8B=9C=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlaceReviewController.java | 70 +++--- .../converter/PlaceReviewConverter.java | 4 +- .../dto/PlaceReviewResponseDTO.java | 2 +- .../service/PlaceReviewQueryServiceImpl.java | 221 +++++++++--------- 4 files changed, 148 insertions(+), 149 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java index 9b12e777..8d0a3680 100644 --- a/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java +++ b/src/main/java/com/otakumap/domain/place_review/controller/PlaceReviewController.java @@ -1,35 +1,35 @@ -//package com.otakumap.domain.place_review.controller; -// -//import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; -//import com.otakumap.domain.place_review.service.PlaceReviewQueryService; -//import com.otakumap.global.apiPayload.ApiResponse; -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.Parameter; -//import io.swagger.v3.oas.annotations.Parameters; -//import lombok.RequiredArgsConstructor; -//import org.springframework.web.bind.annotation.*; -// -//@RestController -//@RequiredArgsConstructor -//@RequestMapping("/api") -//public class PlaceReviewController { -// private final PlaceReviewQueryService placeReviewQueryService; -// -// @GetMapping("/places/{placeId}/reviews") -// @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") -// @Parameters({ -// @Parameter(name = "placeId", description = "특정 장소의 id 입니다."), -// @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), -// @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), -// @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") -// }) -// public ApiResponse getPlaceReviewList(@PathVariable Long placeId, -// @RequestParam(defaultValue = "0") int page, -// @RequestParam(defaultValue = "10") int size, -// @RequestParam(defaultValue = "latest") String sort) { -// -// PlaceReviewResponseDTO.PlaceAnimationReviewDTO results = placeReviewQueryService.getReviewsByPlace(placeId, page, size, sort); -// -// return ApiResponse.onSuccess(results); -// } -//} +package com.otakumap.domain.place_review.controller; + +import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +import com.otakumap.domain.place_review.service.PlaceReviewQueryService; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class PlaceReviewController { + private final PlaceReviewQueryService placeReviewQueryService; + + @GetMapping("/places/{placeId}/reviews") + @Operation(summary = "특정 장소의 전체 후기 조회", description = "특정 장소의 후기들을 조회합니다") + @Parameters({ + @Parameter(name = "placeId", description = "특정 장소의 id 입니다."), + @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", example = "0"), + @Parameter(name = "size", description = "한 페이지당 최대 리뷰 수", example = "10"), + @Parameter(name = "sort", description = "정렬 기준 (latest 또는 views)", example = "latest") + }) + public ApiResponse getPlaceReviewList(@PathVariable Long placeId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "latest") String sort) { + + PlaceReviewResponseDTO.PlaceAnimationReviewDTO results = placeReviewQueryService.getReviewsByPlace(placeId, page, size, sort); + + return ApiResponse.onSuccess(results); + } +} diff --git a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java index 1af6cc89..abdabf82 100644 --- a/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_review/converter/PlaceReviewConverter.java @@ -8,7 +8,6 @@ import com.otakumap.domain.place_review.entity.PlaceReview; import java.util.List; -import java.util.stream.Collectors; public class PlaceReviewConverter { // PlaceReview -> PlaceReviewDTO 변환 @@ -16,7 +15,8 @@ public static PlaceReviewResponseDTO.PlaceReviewDTO toPlaceReviewDTO(PlaceReview return PlaceReviewResponseDTO.PlaceReviewDTO.builder() .reviewId(placeReview.getId()) - .placeIds(placeReview.getPlaceList().stream().map(prp -> prp.getPlace().getId()).collect(Collectors.toList())) // 해령: placeId -> placeIds로 변경 + .placeId(placeReview.getPlaceList().stream() + .map(prp -> prp.getPlace().getId()).toList().get(0)) .title(placeReview.getTitle()) .content(placeReview.getContent()) .view(placeReview.getView()) diff --git a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java index 5d7d2184..ab1f0ac0 100644 --- a/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_review/dto/PlaceReviewResponseDTO.java @@ -17,7 +17,7 @@ public class PlaceReviewResponseDTO { @AllArgsConstructor public static class PlaceReviewDTO { private Long reviewId; - private List placeIds; // 해령: ids로 수정 + private Long placeId; private String title; private String content; private Long view; diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index d84da32c..957b48ed 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -1,111 +1,110 @@ -//package com.otakumap.domain.place_review.service; -// -//import com.otakumap.domain.animation.entity.Animation; -//import com.otakumap.domain.hash_tag.converter.HashTagConverter; -//import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; -//import com.otakumap.domain.mapping.PlaceAnimation; -//import com.otakumap.domain.mapping.PlaceReviewPlace; -//import com.otakumap.domain.place.entity.Place; -//import com.otakumap.domain.place.repository.PlaceRepository; -//import com.otakumap.domain.place_review.converter.PlaceReviewConverter; -//import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; -//import com.otakumap.domain.place_review.entity.PlaceReview; -//import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -//import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; -//import com.otakumap.domain.place_short_review.entity.PlaceShortReview; -//import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; -//import com.otakumap.global.apiPayload.code.status.ErrorStatus; -//import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; -//import lombok.RequiredArgsConstructor; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.*; -//import java.util.stream.Collectors; -// -//@Service -//@RequiredArgsConstructor -//@Transactional(readOnly = true) -//public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { -// -// private final PlaceRepository placeRepository; -// private final PlaceReviewRepository placeReviewRepository; -// private final PlaceShortReviewRepository placeShortReviewRepository; -// private final PlaceReviewPlaceRepository placeReviewPlaceRepository; -// -// @Override -// public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { -// -// Place place = placeRepository.findById(placeId) -// .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); -// -// // 전체 한 줄 리뷰 평균 별점 구하기 -// List placeShortReviewList = placeShortReviewRepository.findAllByPlace(place); -// double avgRating = placeShortReviewList.stream() -// .mapToDouble(PlaceShortReview::getRating) -// .average() -// .orElse(0.0); -// Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); -// -// // 전체 리뷰 리스트 -// List placeReviewPlaces = placeReviewPlaceRepository.findByPlace(place); -// List allReviews = placeReviewPlaces.stream() -// .map(prp -> placeReviewRepository.findById(prp.getPlaceReview().getId())) -// .flatMap(Optional::stream) -// .toList(); -// -// // 애니메이션별 해시태그 -// List placeAnimations = allReviews.stream() -// .map(PlaceReview::getPlaceAnimation) -// .filter(Objects::nonNull) -// .distinct() -// .toList(); -// Map> animationHashTagMap = -// placeAnimations.stream() -// .collect(Collectors.groupingBy( -// PlaceAnimation::getAnimation, // 그룹의 키 : PlaceAnimation이 참조하는 Animation -// -// Collectors.flatMapping( -// pa -> pa.getPlaceAnimationHashTags().stream() -// .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())), -// Collectors.toList() -// ) -// )); -// -// // 애니메이션별로 리뷰 그룹화 -// Map> reviewsByAnimation = allReviews.stream() -// .collect(Collectors.groupingBy(review -> review.getPlaceAnimation().getAnimation())); -// -// // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 -// List animationGroups = paginateReviews(reviewsByAnimation, animationHashTagMap, page, size); -// -// // 총 리뷰 수 계산 -// long totalReviews = reviewsByAnimation.values().stream() -// .mapToLong(List::size) -// .sum(); -// -// return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); -// } -// -// private List paginateReviews(Map> reviewsByAnimation, -// Map> animationHashTagMap, -// int page, int size) { -// -// return reviewsByAnimation.entrySet().stream() -// .map(entry -> { -// Animation animation = entry.getKey(); -// List reviews = entry.getValue(); -// // 각 애니메이션에 해당하는 해시태그 목록을 가져오기 -// List hashTagsForAnimation = animationHashTagMap.getOrDefault(animation, Collections.emptyList()); -// -// int fromIndex = Math.min(page * size, reviews.size()); -// int toIndex = Math.min(fromIndex + size, reviews.size()); -// -// List pagedReviews = reviews.subList(fromIndex, toIndex); -// -// return PlaceReviewConverter.toAnimationReviewGroupDTO(animation, pagedReviews, hashTagsForAnimation); -// }) -// .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 -// .toList(); -// } -//} +package com.otakumap.domain.place_review.service; + +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +import com.otakumap.domain.mapping.PlaceReviewPlace; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_review.converter.PlaceReviewConverter; +import com.otakumap.domain.place_review.dto.PlaceReviewResponseDTO; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.place_review_place.repository.PlaceReviewPlaceRepository; +import com.otakumap.domain.place_short_review.entity.PlaceShortReview; +import com.otakumap.domain.place_short_review.repository.PlaceShortReviewRepository; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PlaceReviewQueryServiceImpl implements PlaceReviewQueryService { + + private final PlaceRepository placeRepository; + private final PlaceReviewRepository placeReviewRepository; + private final PlaceShortReviewRepository placeShortReviewRepository; + private final PlaceReviewPlaceRepository placeReviewPlaceRepository; + + @Override + public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long placeId, int page, int size, String sort) { + + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + + // 전체 한 줄 리뷰 평균 별점 구하기 + List placeShortReviewList = placeShortReviewRepository.findAllByPlace(place); + double avgRating = placeShortReviewList.stream() + .mapToDouble(PlaceShortReview::getRating) + .average() + .orElse(0.0); + Float finalAvgRating = (float)(Math.round(avgRating * 10) / 10.0); + + // 전체 리뷰 리스트 + List placeReviewPlaces = placeReviewPlaceRepository.findByPlace(place); + List allReviews = placeReviewPlaces.stream() + .map(prp -> placeReviewRepository.findById(prp.getPlaceReview().getId())) + .flatMap(Optional::stream) + .distinct() + .toList(); + + // 애니메이션별 해시태그 (모든 리뷰에서 애니메이션 추출 -> 각 애니메이션에 연결된 hashtag 추출) + List animations = allReviews.stream() + .map(PlaceReview::getAnimation) + .filter(Objects::nonNull) + .distinct() + .toList(); + // 애니메이션별 해시태그 (각 애니메이션에 연결된 hashtag 추출) + Map> animationHashTagMap = + animations.stream() + .collect(Collectors.toMap( + animation -> animation, + animation -> animation.getAnimationHashtags().stream() + .map(ah -> HashTagConverter.toHashTagDTO(ah.getHashTag())) + .collect(Collectors.toList()) + )); + + // 애니메이션별로 리뷰 그룹화 + Map> reviewsByAnimation = allReviews.stream() + .filter(review -> review.getAnimation() != null) + .collect(Collectors.groupingBy(PlaceReview::getAnimation)); + + // 애니메이션 그룹마다 그 안에 속한 리뷰들 페이징 적용 + List animationGroups = paginateReviews(reviewsByAnimation, animationHashTagMap, page, size); + + // 총 리뷰 수 계산 + long totalReviews = reviewsByAnimation.values().stream() + .mapToLong(List::size) + .sum(); + + return PlaceReviewConverter.toPlaceAnimationReviewDTO(place, totalReviews, animationGroups, finalAvgRating); + } + + private List paginateReviews(Map> reviewsByAnimation, + Map> animationHashTagMap, + int page, int size) { + return reviewsByAnimation.entrySet().stream() + .map(entry -> { + Animation animation = entry.getKey(); + List reviews = entry.getValue(); + + // 각 애니메이션에 해당하는 해시태그 목록을 가져오기 + List hashTagsForAnimation = animationHashTagMap.getOrDefault(animation, Collections.emptyList()); + + int fromIndex = Math.min(page * size, reviews.size()); + int toIndex = Math.min(fromIndex + size, reviews.size()); + List pagedReviews = reviews.subList(fromIndex, toIndex); + + return PlaceReviewConverter.toAnimationReviewGroupDTO(animation, pagedReviews, hashTagsForAnimation); + }) + .filter(group -> !group.getReviews().isEmpty()) // 빈 그룹 제외 + .toList(); + } + +} From 317af290b7cabcae6928ebeadb343a814cf04354 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 17 Feb 2025 04:58:45 +0900 Subject: [PATCH 433/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8/?= =?UTF-8?q?=EC=9E=91=ED=92=88=EB=AA=85=20=EC=A7=80=EB=8F=84=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=EC=9D=98=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=ED=95=B4=EC=8B=9C=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventLocationRepository.java | 1 + .../search/controller/SearchController.java | 40 +-- .../search/service/SearchServiceImpl.java | 312 +++++++++--------- 3 files changed, 177 insertions(+), 176 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java index 8cd90bf1..48cab7b7 100644 --- a/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java +++ b/src/main/java/com/otakumap/domain/event_location/repository/EventLocationRepository.java @@ -6,4 +6,5 @@ import java.util.List; public interface EventLocationRepository extends JpaRepository { + List findByLatAndLng(Double lat, Double lng); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/search/controller/SearchController.java b/src/main/java/com/otakumap/domain/search/controller/SearchController.java index 9c348a69..5eb2c0ad 100644 --- a/src/main/java/com/otakumap/domain/search/controller/SearchController.java +++ b/src/main/java/com/otakumap/domain/search/controller/SearchController.java @@ -17,23 +17,23 @@ import java.util.List; -//@RestController -//@RequiredArgsConstructor -//@RequestMapping("/api") -//public class SearchController { -// -// private final SearchService searchService; -// -// @GetMapping("/map/search") -// @Operation(summary = "이벤트/작품명 지도 검색", description = "키워드로 이벤트/장소명/애니메이션 제목을 검색하여 관련된 장소 위치와 그 위치의 이벤트를 조회합니다.") -// @ApiResponses({ -// @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), -// }) -// @Parameters({ -// @Parameter(name = "keyword", description = "검색 키워드입니다."), -// }) -// public ApiResponse> getSearchedPlaceInfoList(@CurrentUser(required=false) User user, @RequestParam String keyword) { -// -// return ApiResponse.onSuccess(searchService.getSearchedResult(user, keyword)); -// } -//} +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class SearchController { + + private final SearchService searchService; + + @GetMapping("/map/search") + @Operation(summary = "이벤트/작품명 지도 검색", description = "키워드로 이벤트/장소명/애니메이션 제목을 검색하여 관련된 장소 위치와 그 위치의 이벤트를 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "keyword", description = "검색 키워드입니다."), + }) + public ApiResponse> getSearchedPlaceInfoList(@CurrentUser(required=false) User user, @RequestParam String keyword) { + + return ApiResponse.onSuccess(searchService.getSearchedResult(user, keyword)); + } +} diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index e9259c59..cfa84de7 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -1,156 +1,156 @@ -//package com.otakumap.domain.search.service; -// -//import com.otakumap.domain.animation.converter.AnimationConverter; -//import com.otakumap.domain.animation.dto.AnimationResponseDTO; -//import com.otakumap.domain.event.converter.EventConverter; -//import com.otakumap.domain.event.dto.EventResponseDTO; -//import com.otakumap.domain.event.entity.Event; -//import com.otakumap.domain.event.entity.enums.EventStatus; -//import com.otakumap.domain.event_like.repository.EventLikeRepository; -//import com.otakumap.domain.event_location.entity.EventLocation; -//import com.otakumap.domain.event_location.repository.EventLocationRepository; -//import com.otakumap.domain.hash_tag.converter.HashTagConverter; -//import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; -//import com.otakumap.domain.mapping.EventHashTag; -//import com.otakumap.domain.mapping.repository.EventHashTagRepository; -//import com.otakumap.domain.place.DTO.PlaceResponseDTO; -//import com.otakumap.domain.place.converter.PlaceConverter; -//import com.otakumap.domain.place.entity.Place; -//import com.otakumap.domain.place.repository.PlaceRepository; -//import com.otakumap.domain.place_like.repository.PlaceLikeRepository; -//import com.otakumap.domain.search.converter.SearchConverter; -//import com.otakumap.domain.search.dto.SearchResponseDTO; -//import com.otakumap.domain.search.repository.SearchRepositoryCustom; -//import com.otakumap.domain.user.entity.User; -//import lombok.RequiredArgsConstructor; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.*; -//import java.util.stream.Collectors; -//import java.util.stream.Stream; -// -//@Service -//@RequiredArgsConstructor -//public class SearchServiceImpl implements SearchService { -// -// private final SearchRepositoryCustom searchRepository; -// private final EventLocationRepository eventLocationRepository; -// private final EventLikeRepository eventLikeRepository; -// private final EventHashTagRepository eventHashTagRepository; -// private final PlaceLikeRepository placeLikeRepository; -// private final PlaceRepository placeRepository; -// -// @Transactional(readOnly = true) -// @Override -// public List getSearchedResult (User user, String keyword) { -// -// List events = searchRepository.searchEventsByKeyword(keyword); -// List places = searchRepository.searchPlacesByKeyword(keyword); -// -// List safePlaces = places != null ? places : Collections.emptyList(); // 불변 -// List safeEvents = events != null ? events : new ArrayList<>(); // 가변 -// -// // 검색한 결과에 장소가 있을 때 -> 각 장소의 위/경도와 같은 곳에서 진행되고 있는 event를 찾음 -// List newEvents = safePlaces.stream() -// .flatMap(place -> eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()).stream()) -// .map(EventLocation::getEvent) -// .filter(event -> event != null && event.getStatus() != EventStatus.ENDED) -// .toList(); -// -// // 기존에 검색된 이벤트와 합치기 -// List combinedEvents = -// Stream.concat(safeEvents.stream(), newEvents.stream()) -// .distinct() // 중복 이벤트 제거 -// .toList(); -// -// // 이벤트를 위치(위도, 경도) 기준으로 그룹화 -// Map> groupedEvents = combinedEvents.stream() -// .filter(event -> event.getEventLocation() != null) -// .collect(Collectors.groupingBy(event -> -// event.getEventLocation().getLat() + "," + event.getEventLocation().getLng() -// )); -// -// // 장소들을 위치 기준으로 그룹화 (같은 위치에 여러 명소가 있을 수 있으므로 그룹화) -// Map> groupedPlaces = safePlaces.stream() -// .collect(Collectors.groupingBy(place -> place.getLat() + "," + place.getLng())); -// -// // 만약 groupedEvents에는 존재하는 위치(key)가 groupedPlaces에 없다면, -// // 해당 위치의 장소들을 조회해서 groupedPlaces에 추가 -// for (String key : groupedEvents.keySet()) { -// if (!groupedPlaces.containsKey(key)) { -// String[] parts = key.split(","); -// Double lat = Double.valueOf(parts[0]); -// Double lng = Double.valueOf(parts[1]); -// -// List additionalPlaces = placeRepository.findByLatAndLng(lat, lng); -// -// if (additionalPlaces != null && !additionalPlaces.isEmpty()) { -// groupedPlaces.put(key, additionalPlaces); -// } -// } -// } -// -// // 모든 위치 키의 합집합 -// Set allKeys = new HashSet<>(); -// allKeys.addAll(groupedEvents.keySet()); -// allKeys.addAll(groupedPlaces.keySet()); -// -// // 각 위치 키별로 이벤트와 장소 정보를 DTO로 변환하여 SearchResultDTO 생성 -// return allKeys.stream().map(key -> { -// String[] parts = key.split(","); -// Double lat = Double.valueOf(parts[0]); -// Double lng = Double.valueOf(parts[1]); -// -// // 해당 위치의 이벤트들을 DTO로 변환 -// List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) -// .stream().map(event -> { -// boolean isLiked = false; -// if(user != null) { -// isLiked = eventLikeRepository.existsByUserAndEvent(user, event); -// } -// -// // 이벤트에 연결된 해시태그 조회 -// List eventHashTags = eventHashTagRepository.findByEvent(event); -// List hashTags = eventHashTags.stream() -// .map(eht -> HashTagConverter.toHashTagDTO(eht.getHashTag())) -// .collect(Collectors.toList()); -// -// // eventAnimationList를 이용해 애니메이션 제목을 추출 (여러 개가 있을 경우 콤마로 연결) -// String animationTitle = event.getEventAnimationList().stream() -// .map(ea -> ea.getAnimation().getName()) -// .collect(Collectors.joining(", ")); -// -// return EventConverter.toSearchedEventInfoDTO(event, isLiked, animationTitle, hashTags); -// }) -// .collect(Collectors.toList()); -// -// // 장소 DTO 변환 -// List placeDTOs = groupedPlaces.getOrDefault(key, Collections.emptyList()) -// .stream().map(place -> { -// // 각 장소의 모든 PlaceAnimation을 AnimationInfoDTO 리스트로 변환 (애니메이션별 좋아요 여부 계산) -// List animationDTOs = place.getPlaceAnimationList().stream() -// .map(placeAnimation -> { -// boolean isLiked = false; -// // 각 장소의 애니메이션별 좋아요 여부 -// if (user != null) { -// isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); -// } -// -// List hashTags = placeAnimation.getPlaceAnimationHashTags().stream() -// .map(pah -> HashTagConverter.toHashTagDTO(pah.getHashTag())) -// .collect(Collectors.toList()); -// -// return AnimationConverter.toAnimationInfoDTO(placeAnimation, isLiked, hashTags); -// }) -// .collect(Collectors.toList()); -// -// return PlaceConverter.toSearchedPlaceInfoDTO(place, animationDTOs); -// }) -// .toList(); -// -// return SearchConverter.toSearchResultDTO(eventDTOs, placeDTOs, lat, lng); -// }).collect(Collectors.toList()); -// } -// -//} +package com.otakumap.domain.search.service; + +import com.otakumap.domain.animation.converter.AnimationConverter; +import com.otakumap.domain.animation.dto.AnimationResponseDTO; +import com.otakumap.domain.event.converter.EventConverter; +import com.otakumap.domain.event.dto.EventResponseDTO; +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.entity.enums.EventStatus; +import com.otakumap.domain.event_like.repository.EventLikeRepository; +import com.otakumap.domain.event_location.entity.EventLocation; +import com.otakumap.domain.event_location.repository.EventLocationRepository; +import com.otakumap.domain.hash_tag.converter.HashTagConverter; +import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; +import com.otakumap.domain.place.DTO.PlaceResponseDTO; +import com.otakumap.domain.place.converter.PlaceConverter; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import com.otakumap.domain.search.converter.SearchConverter; +import com.otakumap.domain.search.dto.SearchResponseDTO; +import com.otakumap.domain.search.repository.SearchRepositoryCustom; +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +@RequiredArgsConstructor +public class SearchServiceImpl implements SearchService { + + private final SearchRepositoryCustom searchRepository; + private final EventLocationRepository eventLocationRepository; + private final EventLikeRepository eventLikeRepository; + private final PlaceLikeRepository placeLikeRepository; + private final PlaceRepository placeRepository; + + @Transactional(readOnly = true) + @Override + public List getSearchedResult (User user, String keyword) { + + List events = searchRepository.searchEventsByKeyword(keyword); + List places = searchRepository.searchPlacesByKeyword(keyword); + + List safePlaces = places != null ? places : Collections.emptyList(); // 불변 + List safeEvents = events != null ? events : new ArrayList<>(); // 가변 + + // 검색한 결과에 장소가 있을 때 -> 각 장소의 위/경도와 같은 곳에서 진행되고 있는 event를 찾음 + List newEvents = safePlaces.stream() + .flatMap(place -> eventLocationRepository.findByLatAndLng(place.getLat(), place.getLng()).stream()) + .map(EventLocation::getEvent) + .filter(event -> event != null && event.getStatus() != EventStatus.ENDED) + .toList(); + + // 기존에 검색된 이벤트와 합치기 + List combinedEvents = + Stream.concat(safeEvents.stream(), newEvents.stream()) + .distinct() // 중복 이벤트 제거 + .toList(); + + // 이벤트를 위치(위도, 경도) 기준으로 그룹화 + Map> groupedEvents = combinedEvents.stream() + .filter(event -> event.getEventLocation() != null) + .collect(Collectors.groupingBy(event -> + event.getEventLocation().getLat() + "," + event.getEventLocation().getLng() + )); + + // 장소들을 위치 기준으로 그룹화 (같은 위치에 여러 명소가 있을 수 있으므로 그룹화) + Map> groupedPlaces = safePlaces.stream() + .collect(Collectors.groupingBy(place -> place.getLat() + "," + place.getLng())); + + // 만약 groupedEvents에는 존재하는 위치(key)가 groupedPlaces에 없다면, + // 해당 위치의 장소들을 조회해서 groupedPlaces에 추가 + for (String key : groupedEvents.keySet()) { + if (!groupedPlaces.containsKey(key)) { + String[] parts = key.split(","); + Double lat = Double.valueOf(parts[0]); + Double lng = Double.valueOf(parts[1]); + + List additionalPlaces = placeRepository.findByLatAndLng(lat, lng); + + if (additionalPlaces != null && !additionalPlaces.isEmpty()) { + groupedPlaces.put(key, additionalPlaces); + } + } + } + + // 모든 위치 키의 합집합 + Set allKeys = new HashSet<>(); + allKeys.addAll(groupedEvents.keySet()); + allKeys.addAll(groupedPlaces.keySet()); + + // 각 위치 키별로 이벤트와 장소 정보를 DTO로 변환하여 SearchResultDTO 생성 + return allKeys.stream().map(key -> { + String[] parts = key.split(","); + Double lat = Double.valueOf(parts[0]); + Double lng = Double.valueOf(parts[1]); + + // 해당 위치의 이벤트들을 DTO로 변환 + List eventDTOs = groupedEvents.getOrDefault(key, Collections.emptyList()) + .stream().map(event -> { + boolean isLiked = false; + if(user != null) { + isLiked = eventLikeRepository.existsByUserAndEvent(user, event); + } + + // 이벤트에 연결된 해시태그 조회 + List eventHashTags = event.getEventAnimationList().stream() + .flatMap(ea -> ea.getAnimation().getAnimationHashtags().stream()) + .map(ah -> HashTagConverter.toHashTagDTO(ah.getHashTag())) + .distinct() + .toList(); + + // eventAnimationList를 이용해 애니메이션 제목을 추출 (여러 개가 있을 경우 콤마로 연결) + String animationTitle = event.getEventAnimationList().stream() + .map(ea -> ea.getAnimation().getName()) + .collect(Collectors.joining(", ")); + + return EventConverter.toSearchedEventInfoDTO(event, isLiked, animationTitle, eventHashTags); + }) + .collect(Collectors.toList()); + + // 장소 DTO 변환 + List placeDTOs = groupedPlaces.getOrDefault(key, Collections.emptyList()) + .stream().map(place -> { + // 각 장소의 모든 PlaceAnimation을 AnimationInfoDTO 리스트로 변환 (애니메이션별 좋아요 여부 계산) + List animationDTOs = place.getPlaceAnimationList().stream() + .map(placeAnimation -> { + boolean isLiked = false; + // 각 장소의 애니메이션별 좋아요 여부 + if (user != null) { + isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); + } + + List placeHashTags = place.getPlaceAnimationList().stream() + .flatMap(pa -> pa.getAnimation().getAnimationHashtags().stream()) + .map(ah -> HashTagConverter.toHashTagDTO(ah.getHashTag())) + .distinct() + .toList(); + + return AnimationConverter.toAnimationInfoDTO(placeAnimation, isLiked, placeHashTags); + }) + .collect(Collectors.toList()); + + return PlaceConverter.toSearchedPlaceInfoDTO(place, animationDTOs); + }) + .toList(); + + return SearchConverter.toSearchResultDTO(eventDTOs, placeDTOs, lat, lng); + }).collect(Collectors.toList()); + } + +} From 43a8545f937792ad114424decfcc7cdf07173905 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 17 Feb 2025 05:02:46 +0900 Subject: [PATCH 434/516] =?UTF-8?q?Comment:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/search/service/SearchServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java index cfa84de7..556c9d7c 100644 --- a/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/otakumap/domain/search/service/SearchServiceImpl.java @@ -135,6 +135,7 @@ public List getSearchedResult (User user, Str isLiked = placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation); } + // 해시태그 조회 List placeHashTags = place.getPlaceAnimationList().stream() .flatMap(pa -> pa.getAnimation().getAnimationHashtags().stream()) .map(ah -> HashTagConverter.toHashTagDTO(ah.getHashTag())) From 576e07dcae5e012275537748c8c00905750750d4 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 17 Feb 2025 16:06:21 +0900 Subject: [PATCH 435/516] =?UTF-8?q?Refactor:=20pointAfterCharge=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/point/entity/Point.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index c72acca5..514ac170 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -38,9 +38,6 @@ public class Point extends BaseEntity { @Column(name = "charged_by") private String chargedBy; - @Column(name = "point_after_charge") - private Long pointAfterCharge; - @Column(name = "merchant_uid", unique = true, nullable = false) private String merchantUid; From 44ec84b942115893db06ad0375cf782fb542aabc Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Mon, 17 Feb 2025 16:34:03 +0900 Subject: [PATCH 436/516] =?UTF-8?q?Revert=20"Feat:=20=EA=B2=B0=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EA=B3=BC=20=EA=B4=80=EB=A0=A8=EB=90=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20entity"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 10 +--- .../domain/payment/entity/Payment.java | 45 ----------------- .../domain/payment/enums/PaymentStatus.java | 5 -- .../otakumap/domain/point/entity/Point.java | 50 ------------------- 4 files changed, 1 insertion(+), 109 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/payment/entity/Payment.java delete mode 100644 src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java delete mode 100644 src/main/java/com/otakumap/domain/point/entity/Point.java diff --git a/build.gradle b/build.gradle index 7e7b039b..ebcf7408 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,6 @@ configurations { repositories { mavenCentral() - maven { url 'https://jitpack.io' } } jar{ @@ -68,19 +67,12 @@ dependencies { implementation 'com.google.code.gson:gson' // 구글 places api 관련 - // implementation 'com.google.maps:google-maps-services:0.42.2' +// implementation 'com.google.maps:google-maps-services:0.42.2' // Jackson: JSON 응답을 파싱 implementation 'com.fasterxml.jackson.core:jackson-databind' // AWS S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - - // iamport 결제 관련 - implementation group: 'com.github.iamport', name: 'iamport-rest-client-java', version: '0.2.22' - implementation group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.9.0' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' - implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3' - implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.3.0' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/payment/entity/Payment.java b/src/main/java/com/otakumap/domain/payment/entity/Payment.java deleted file mode 100644 index 22e24b66..00000000 --- a/src/main/java/com/otakumap/domain/payment/entity/Payment.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.otakumap.domain.payment.entity; - -import com.otakumap.domain.payment.enums.PaymentStatus; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Entity -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Table(name = "payment") -public class Payment extends BaseEntity{ - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column(name = "imp_uid", nullable = false) - private String impUid; - - @Column(name = "merchant_uid", nullable = false) - private String merchantUid; - - @Column(nullable = false) - private Long amount; - - @Column(name = "verified_at") - private LocalDateTime verifiedAt; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PaymentStatus status = PaymentStatus.PENDING; -} diff --git a/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java b/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java deleted file mode 100644 index 37466826..00000000 --- a/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.otakumap.domain.payment.enums; - -public enum PaymentStatus { - PENDING, PAID, FAILED -} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java deleted file mode 100644 index 514ac170..00000000 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.otakumap.domain.point.entity; - -import com.otakumap.domain.payment.enums.PaymentStatus; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.CreationTimestamp; - -import java.time.LocalDateTime; - -@Entity -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Table(name = "point") -public class Point extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column(nullable = false) - private Long point; - - @Column(name = "charged_at", nullable = false, updatable = false) - @CreationTimestamp - private LocalDateTime chargedAt; - - @Column(name = "charged_by") - private String chargedBy; - - @Column(name = "merchant_uid", unique = true, nullable = false) - private String merchantUid; - - @Column(name = "imp_uid") - private String impUid; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PaymentStatus status = PaymentStatus.PENDING; -} From eeeda73b9232b0c8b025ba4761763fd93b86737e Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 17 Feb 2025 19:43:14 +0900 Subject: [PATCH 437/516] =?UTF-8?q?feature:=20=EA=B5=AC=EB=A7=A4=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B8=B0=EB=B3=B8=20entity=20?= =?UTF-8?q?&=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/payment/entity/Payment.java | 46 +++++++++++++++++ .../domain/payment/enums/PaymentStatus.java | 5 ++ .../otakumap/domain/point/entity/Point.java | 51 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/payment/entity/Payment.java create mode 100644 src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java create mode 100644 src/main/java/com/otakumap/domain/point/entity/Point.java diff --git a/src/main/java/com/otakumap/domain/payment/entity/Payment.java b/src/main/java/com/otakumap/domain/payment/entity/Payment.java new file mode 100644 index 00000000..adef9e15 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/entity/Payment.java @@ -0,0 +1,46 @@ +package com.otakumap.domain.payment.entity; + + +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "payment") +public class Payment extends BaseEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "imp_uid", nullable = false) + private String impUid; + + @Column(name = "merchant_uid", nullable = false) + private String merchantUid; + + @Column(nullable = false) + private Long amount; + + @Column(name = "verified_at") + private LocalDateTime verifiedAt; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PaymentStatus status = PaymentStatus.PENDING; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java b/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java new file mode 100644 index 00000000..37466826 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/enums/PaymentStatus.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.payment.enums; + +public enum PaymentStatus { + PENDING, PAID, FAILED +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java new file mode 100644 index 00000000..2b1f85f3 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -0,0 +1,51 @@ +package com.otakumap.domain.point.entity; + + +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "point") +public class Point extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false) + private Long point; + + @Column(name = "charged_at", nullable = false, updatable = false) + @CreationTimestamp + private LocalDateTime chargedAt; + + @Column(name = "charged_by") + private String chargedBy; + + @Column(name = "merchant_uid", unique = true, nullable = false) + private String merchantUid; + + @Column(name = "imp_uid") + private String impUid; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PaymentStatus status = PaymentStatus.PENDING; +} \ No newline at end of file From ba45f039796a035c852e37c2a7a0b0667f634218 Mon Sep 17 00:00:00 2001 From: mk-star Date: Mon, 17 Feb 2025 19:46:07 +0900 Subject: [PATCH 438/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/converter/PlaceLikeConverter.java | 4 +++- .../otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java index d5045e22..9eeccec8 100644 --- a/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java +++ b/src/main/java/com/otakumap/domain/place_like/converter/PlaceLikeConverter.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.converter; +import com.otakumap.domain.animation.converter.AnimationConverter; import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; @@ -49,8 +50,9 @@ public static PlaceLikeResponseDTO.FavoriteResultDTO toFavoriteResultDTO(PlaceLi public static PlaceLikeResponseDTO.PlaceLikeDetailDTO placeLikeDetailDTO(PlaceLike placeLike, Place place) { return PlaceLikeResponseDTO.PlaceLikeDetailDTO.builder() .placeLikeId(placeLike.getId()) + .placeId(place.getId()) .placeName(place.getName()) - .animationName(placeLike.getPlaceAnimation().getAnimation().getName()) + .animation(AnimationConverter.animationResultDTO(placeLike.getPlaceAnimation().getAnimation())) .lat(place.getLat()) .lng(place.getLng()) .isLiked(Boolean.TRUE) // 저장한 장소를 조회하는 거니까 항상 true diff --git a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java index 0434c5e6..ed9e2253 100644 --- a/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_like/dto/PlaceLikeResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_like.dto; +import com.otakumap.domain.animation.dto.AnimationResponseDTO; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -48,8 +49,9 @@ public static class FavoriteResultDTO { @AllArgsConstructor public static class PlaceLikeDetailDTO { Long placeLikeId; + Long placeId; String placeName; - String animationName; + AnimationResponseDTO.AnimationResultDTO animation; Double lat; Double lng; Boolean isLiked; From 751ddbe2c0e25ec6cdba59b270090eb580424324 Mon Sep 17 00:00:00 2001 From: mk-star Date: Mon, 17 Feb 2025 19:53:43 +0900 Subject: [PATCH 439/516] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8A=B8=20=EB=82=B4?= =?UTF-8?q?=20=ED=8A=B9=EC=A0=95=20=EC=9E=A5=EC=86=8C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java | 2 +- .../com/otakumap/domain/place/converter/PlaceConverter.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java index 2090b8b7..add40307 100644 --- a/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place/DTO/PlaceResponseDTO.java @@ -40,7 +40,7 @@ public static class PlaceDetailDTO { private Double longitude; private Boolean isFavorite; private Boolean isLiked; - private String animationName; + private AnimationResponseDTO.AnimationResultDTO animation; private List hashtags; } diff --git a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java index ce6fd518..5dd4ba86 100644 --- a/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java +++ b/src/main/java/com/otakumap/domain/place/converter/PlaceConverter.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place.converter; +import com.otakumap.domain.animation.converter.AnimationConverter; import com.otakumap.domain.animation.dto.AnimationResponseDTO; import com.otakumap.domain.hash_tag.converter.HashTagConverter; import com.otakumap.domain.hash_tag.dto.HashTagResponseDTO; @@ -38,7 +39,7 @@ public static PlaceResponseDTO.PlaceDetailDTO toPlaceDetailDTO(Place place, Plac .longitude(place.getLng()) .isFavorite(place.getIsFavorite()) .isLiked(isLiked) - .animationName(placeAnimation.getAnimation().getName()) + .animation(AnimationConverter.animationResultDTO(placeAnimation.getAnimation())) .hashtags(hashtags) .build(); } From d5825fa3eab1d57acd6432c5fab4de77b84b4e43 Mon Sep 17 00:00:00 2001 From: mk-star Date: Mon, 17 Feb 2025 20:36:47 +0900 Subject: [PATCH 440/516] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84-=20=EC=A7=80=EB=8F=84=EC=B0=BD=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=95=98=ED=8A=B8=20=EB=88=84=EB=A5=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place_like/controller/PlaceLikeController.java | 14 +++++++++++++- .../place_like/repository/PlaceLikeRepository.java | 3 +++ .../service/PlaceLikeCommandService.java | 1 + .../service/PlaceLikeCommandServiceImpl.java | 9 ++++++++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java index d2d98796..eab157f8 100644 --- a/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java +++ b/src/main/java/com/otakumap/domain/place_like/controller/PlaceLikeController.java @@ -9,6 +9,7 @@ import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; +import com.otakumap.global.validation.annotation.ExistAnimation; import com.otakumap.global.validation.annotation.ExistPlace; import com.otakumap.global.validation.annotation.ExistPlaceLike; import com.otakumap.global.validation.annotation.ExistPlaceListLike; @@ -41,7 +42,7 @@ public ApiResponse getPlaceLikeLis return ApiResponse.onSuccess(placeLikeQueryService.getPlaceLikeList(user, isFavorite, lastId, limit)); } - @Operation(summary = "저장된 장소 삭제", description = "저장된 장소를 삭제합니다.") + @Operation(summary = "저장된 장소 삭제 - 나의 좋아요 > 찜한 장소", description = "나의 좋아요 > 찜한 장소에서 저장된 장소를 삭제합니다.") @DeleteMapping("") @Parameters({ @Parameter(name = "placeIds", description = "저장된 장소 id List"), @@ -51,6 +52,17 @@ public ApiResponse deletePlaceLike(@RequestParam(required = false) @Exis return ApiResponse.onSuccess("저장된 장소가 성공적으로 삭제되었습니다"); } + @Operation(summary = "저장된 장소 삭제 - 지도창에서 하트 누르기", description = "지도창에서 하트를 눌러서 저장된 장소를 삭제합니다.") + @DeleteMapping("/{placeId}") + @Parameters({ + @Parameter(name = "placeId", description = "장소 id"), + @Parameter(name = "animationId", description = "장소에 해당하는 애니메이션 id"), + }) + public ApiResponse deletePlaceLikeOnMap(@PathVariable @ExistPlace Long placeId, @RequestParam @ExistAnimation Long animationId, @CurrentUser User user) { + placeLikeCommandService.deletePlaceLike(placeId, animationId, user); + return ApiResponse.onSuccess("저장된 장소가 성공적으로 삭제되었습니다"); + } + @Operation(summary = "장소 저장", description = "장소를 저장합니다.") @PostMapping("/{placeId}") @Parameters({ diff --git a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java index 1c8d30ab..bc42c498 100644 --- a/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java +++ b/src/main/java/com/otakumap/domain/place_like/repository/PlaceLikeRepository.java @@ -5,6 +5,9 @@ import com.otakumap.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface PlaceLikeRepository extends JpaRepository { boolean existsByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); + Optional findByUserAndPlaceAnimation(User user, PlaceAnimation placeAnimation); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java index dc182801..ba025673 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandService.java @@ -10,6 +10,7 @@ @Service public interface PlaceLikeCommandService { void deletePlaceLike(List placeIds); + void deletePlaceLike(Long placeId, Long animationId, User user); void savePlaceLike(User user, Long placeId, PlaceLikeRequestDTO.SavePlaceLikeDTO request); PlaceLike favoritePlaceLike(Long placeLikeId, PlaceLikeRequestDTO.FavoriteDTO request); } diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java index 9e3e00e2..e4693dcf 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeCommandServiceImpl.java @@ -23,7 +23,6 @@ public class PlaceLikeCommandServiceImpl implements PlaceLikeCommandService { private final PlaceLikeRepository placeLikeRepository; private final EntityManager entityManager; - private final PlaceRepository placeRepository; private final PlaceAnimationRepository placeAnimationRepository; @Override @@ -33,6 +32,14 @@ public void deletePlaceLike(List placeIds) { entityManager.clear(); } + @Override + public void deletePlaceLike(Long placeId, Long animationId, User user) { + PlaceAnimation placeAnimation = placeAnimationRepository.findByPlaceIdAndAnimationId(placeId, animationId).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); + PlaceLike placeLike = placeLikeRepository.findByUserAndPlaceAnimation(user, placeAnimation).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_LIKE_NOT_FOUND)); + + placeLikeRepository.delete(placeLike); + } + @Override public void savePlaceLike(User user, Long placeId, PlaceLikeRequestDTO.SavePlaceLikeDTO request) { PlaceAnimation placeAnimation = placeAnimationRepository.findByPlaceIdAndAnimationId(placeId, request.getAnimationId()).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_ANIMATION_NOT_FOUND)); From 2380987570b70a2ce3e08c041a6f4a5b72aeb3d8 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Mon, 17 Feb 2025 20:59:21 +0900 Subject: [PATCH 441/516] =?UTF-8?q?feature:=20cors=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/global/config/CorsConfig.java | 3 ++- src/main/java/com/otakumap/global/config/WebConfig.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/global/config/CorsConfig.java b/src/main/java/com/otakumap/global/config/CorsConfig.java index ce7f013d..9aa81265 100644 --- a/src/main/java/com/otakumap/global/config/CorsConfig.java +++ b/src/main/java/com/otakumap/global/config/CorsConfig.java @@ -19,7 +19,8 @@ public static CorsConfigurationSource corsConfigurationSource() { "http://localhost:3000", "https://otakumap.netlify.app", "https://deploy-preview-*--otakumap.netlify.app", // 모든 프리뷰 URL 허용 - "https://api.otakumap.site" + "https://api.otakumap.site", + "https://otakumap.world" )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); diff --git a/src/main/java/com/otakumap/global/config/WebConfig.java b/src/main/java/com/otakumap/global/config/WebConfig.java index 658c44b5..21b1f034 100644 --- a/src/main/java/com/otakumap/global/config/WebConfig.java +++ b/src/main/java/com/otakumap/global/config/WebConfig.java @@ -25,7 +25,7 @@ public void addArgumentResolvers(List resolvers) @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:3000", "https://otakumap.netlify.app", "https://deploy-preview-*--otakumap.netlify.app", "https://api.otakumap.site") + .allowedOrigins("http://localhost:3000", "https://otakumap.netlify.app", "https://deploy-preview-*--otakumap.netlify.app", "https://api.otakumap.site", "https://otakumap.world") .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) From ec6992724ec21a880b346c532cdcf5b2b798d1d3 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 17 Feb 2025 21:30:13 +0900 Subject: [PATCH 442/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=9C=EC=A4=84=EB=A6=AC=EB=B7=B0=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94/=EC=8B=AB=EC=96=B4=EC=9A=94=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTO/EventReactionRequestDTO.java | 14 ++++ .../DTO/EventReactionResponseDTO.java | 20 ++++++ .../controller/EventReactionController.java | 28 ++++++++ .../converter/EventReactionConverter.java | 33 +++++++++ .../event_reaction/entity/EventReaction.java | 37 ++++++++++ .../repository/EventReactionRepository.java | 10 +++ .../service/EventReactionCommandService.java | 8 +++ .../EventReactionCommandServiceImpl.java | 67 +++++++++++++++++++ .../entity/EventShortReview.java | 8 ++- 9 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java create mode 100644 src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java b/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java new file mode 100644 index 00000000..36a03fed --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java @@ -0,0 +1,14 @@ +package com.otakumap.domain.event_reaction.DTO; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.Getter; + +public class EventReactionRequestDTO { + @Getter + public static class ReactionRequestDTO { + @Min(0) + @Max(1) + private int reactionType; + } +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java b/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java new file mode 100644 index 00000000..df0f82e4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.event_reaction.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class EventReactionResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReactionResponseDTO { + private Long reviewId; + private Long likes; + private Long dislikes; + private Boolean isLiked; + private Boolean isDisliked; + } +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java b/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java new file mode 100644 index 00000000..5dc92d5f --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.event_reaction.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.event_reaction.DTO.EventReactionResponseDTO; +import com.otakumap.domain.event_reaction.converter.EventReactionConverter; +import com.otakumap.domain.event_reaction.entity.EventReaction; +import com.otakumap.domain.event_reaction.service.EventReactionCommandService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/events/short-reviews/{reviewId}/reaction") +public class EventReactionController { + private final EventReactionCommandService eventReactionCommandService; + + @PostMapping + @Operation(summary = "이벤트 한줄 리뷰에 좋아요/싫어요 남기기 및 취소하기", description = "0을 요청하면 dislike, 1을 요청하면 like이며, 이미 존재하는 반응을 요청하면 취소됩니다.") + public ApiResponse reactToReview( + @CurrentUser User user, @PathVariable Long reviewId, @Valid @RequestBody int reactionType) { + EventReaction eventReaction = eventReactionCommandService.reactToReview(user, reviewId, reactionType); + return ApiResponse.onSuccess(EventReactionConverter.toReactionResponseDTO(eventReaction.getEventShortReview(), eventReaction)); + } +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java new file mode 100644 index 00000000..0e0d8609 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.event_reaction.converter; + +import com.otakumap.domain.event_reaction.DTO.EventReactionResponseDTO; +import com.otakumap.domain.event_reaction.entity.EventReaction; +import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.user.entity.User; + +public class EventReactionConverter { + public static EventReactionResponseDTO.ReactionResponseDTO toReactionResponseDTO(EventShortReview eventShortReview, EventReaction eventReaction) { + return EventReactionResponseDTO.ReactionResponseDTO.builder() + .reviewId(eventShortReview.getId()) + .likes(eventShortReview.getLikes()) + .dislikes(eventShortReview.getDislikes()) + .isLiked(eventReaction.isLiked()) + .isDisliked(eventReaction.isDisliked()) + .build(); + } + + public static EventReaction toLike(User user, EventShortReview eventShortReview, boolean isLiked) { + return EventReaction.builder() + .user(user) + .isLiked(isLiked) + .build(); + } + + public static EventReaction toDislike(User user, EventShortReview eventShortReview, boolean isDisliked) { + return EventReaction.builder() + .user(user) + .eventShortReview(eventShortReview) + .isDisliked(isDisliked) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java b/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java new file mode 100644 index 00000000..63e6f864 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java @@ -0,0 +1,37 @@ +package com.otakumap.domain.event_reaction.entity; + +import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "event_reaction", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "event_short_review_id"})}) +public class EventReaction extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_short_review_id", nullable = false) + private EventShortReview eventShortReview; + + @Column(nullable = false) + private boolean isLiked; + + @Column(nullable = false) + private boolean isDisliked; + + public void updateLiked(boolean isLiked) { this.isLiked = isLiked; } + + public void updateDisliked(boolean isDisliked) { this.isDisliked = isDisliked; } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java b/src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java new file mode 100644 index 00000000..0de9baee --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.event_reaction.repository; + +import com.otakumap.domain.event_reaction.entity.EventReaction; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface EventReactionRepository extends JpaRepository { + Optional findByUserIdAndEventShortReviewId(Long userId, Long eventShortReviewId); +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java b/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java new file mode 100644 index 00000000..517a5b94 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.event_reaction.service; + +import com.otakumap.domain.event_reaction.entity.EventReaction; +import com.otakumap.domain.user.entity.User; + +public interface EventReactionCommandService { + EventReaction reactToReview(User user, Long reviewId, int reactionType); +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java new file mode 100644 index 00000000..8b00d767 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java @@ -0,0 +1,67 @@ +package com.otakumap.domain.event_reaction.service; + +import com.otakumap.domain.event_reaction.converter.EventReactionConverter; +import com.otakumap.domain.event_reaction.entity.EventReaction; +import com.otakumap.domain.event_reaction.repository.EventReactionRepository; +import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.event_short_review.repository.EventShortReviewRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EventReactionCommandServiceImpl implements EventReactionCommandService { + private final EventReactionRepository eventReactionRepository; + private final EventShortReviewRepository eventShortReviewRepository; + + @Override + @Transactional + public EventReaction reactToReview(User user, Long reviewId, int reactionType) { + EventShortReview eventShortReview = eventShortReviewRepository.findById(reviewId).orElseThrow(() -> new ReviewHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); + + EventReaction eventReaction = eventReactionRepository.findByUserIdAndEventShortReviewId(user.getId(), reviewId).orElse(null); + + if (eventReaction == null) { + if (reactionType == 0) { // dislike + eventReaction = EventReactionConverter.toDislike(user, eventShortReview, true); + eventShortReview.updateDislikes(eventShortReview.getDislikes() + 1); + } else { // like + eventReaction = EventReactionConverter.toLike(user, eventShortReview, true); + eventShortReview.updateLikes(eventShortReview.getLikes() + 1); + } + } else { + if (reactionType == 0) { // dislike + if (!eventReaction.isDisliked()) { + eventReaction.updateDisliked(true); + eventReaction.updateLiked(false); + eventShortReview.updateDislikes(eventShortReview.getDislikes() + 1); + if (eventReaction.isLiked()) { + eventShortReview.updateLikes(eventShortReview.getLikes() - 1); + } + } else { + eventReaction.updateDisliked(false); + eventShortReview.updateDislikes(eventShortReview.getDislikes() - 1); + } + } else { // like + if (!eventReaction.isLiked()) { + eventReaction.updateLiked(true); + eventReaction.updateDisliked(false); + eventShortReview.updateLikes(eventShortReview.getLikes() + 1); + if (eventReaction.isDisliked()) { + eventShortReview.updateDislikes(eventShortReview.getDislikes() - 1); + } + } else { + eventReaction.updateLiked(false); + eventShortReview.updateLikes(eventShortReview.getLikes() - 1); + } + } + } + + eventShortReviewRepository.save(eventShortReview); + return eventReactionRepository.save(eventReaction); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java index 687f5a2f..8c3c682f 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java +++ b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java @@ -36,10 +36,10 @@ public class EventShortReview extends BaseEntity { private Float rating; @ColumnDefault("0") - private int likes; + private Long likes; @ColumnDefault("0") - private int dislikes; + private Long dislikes; public void setContent(String content) { this.content = content; @@ -48,4 +48,8 @@ public void setContent(String content) { public void setRating(Float rating) { this.rating = rating; } + + public void updateLikes(Long likes) { this.likes = likes; } + + public void updateDislikes(Long dislikes) { this.dislikes = dislikes; } } From 5edd3eeb6c4f5700a9266c78e284c49be86f8ea6 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Mon, 17 Feb 2025 21:40:00 +0900 Subject: [PATCH 443/516] =?UTF-8?q?Refactor:=20DTO=20->=20dto=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_reaction/controller/EventReactionController.java | 2 +- .../domain/event_reaction/converter/EventReactionConverter.java | 2 +- .../event_reaction/{DTO => dto}/EventReactionRequestDTO.java | 2 +- .../event_reaction/{DTO => dto}/EventReactionResponseDTO.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/otakumap/domain/event_reaction/{DTO => dto}/EventReactionRequestDTO.java (85%) rename src/main/java/com/otakumap/domain/event_reaction/{DTO => dto}/EventReactionResponseDTO.java (90%) diff --git a/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java b/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java index 5dc92d5f..82c8dfc5 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java +++ b/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java @@ -1,7 +1,7 @@ package com.otakumap.domain.event_reaction.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.event_reaction.DTO.EventReactionResponseDTO; +import com.otakumap.domain.event_reaction.dto.EventReactionResponseDTO; import com.otakumap.domain.event_reaction.converter.EventReactionConverter; import com.otakumap.domain.event_reaction.entity.EventReaction; import com.otakumap.domain.event_reaction.service.EventReactionCommandService; diff --git a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java index 0e0d8609..68f76905 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java +++ b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java @@ -1,6 +1,6 @@ package com.otakumap.domain.event_reaction.converter; -import com.otakumap.domain.event_reaction.DTO.EventReactionResponseDTO; +import com.otakumap.domain.event_reaction.dto.EventReactionResponseDTO; import com.otakumap.domain.event_reaction.entity.EventReaction; import com.otakumap.domain.event_short_review.entity.EventShortReview; import com.otakumap.domain.user.entity.User; diff --git a/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java b/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionRequestDTO.java similarity index 85% rename from src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java rename to src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionRequestDTO.java index 36a03fed..b378165c 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionRequestDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.event_reaction.DTO; +package com.otakumap.domain.event_reaction.dto; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; diff --git a/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java b/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionResponseDTO.java similarity index 90% rename from src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java rename to src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionResponseDTO.java index df0f82e4..a98cab7e 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/DTO/EventReactionResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionResponseDTO.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.event_reaction.DTO; +package com.otakumap.domain.event_reaction.dto; import lombok.AllArgsConstructor; import lombok.Builder; From b1a698ad94884a79bfeefcfb4593dcccda521d31 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 18 Feb 2025 10:32:05 +0900 Subject: [PATCH 444/516] =?UTF-8?q?Refactor:=20=EC=A7=80=EB=8F=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9E=A5=EC=86=8C=20=EB=B0=8F=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EB=B3=B4=20=EB=B3=B4=EA=B8=B0=20API=20?= =?UTF-8?q?=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/map/controller/MapController.java | 48 +++--- .../domain/map/converter/MapConverter.java | 6 +- .../domain/map/dto/MapResponseDTO.java | 2 + .../repository/MapRepositoryCustomImpl.java | 156 +++++++++--------- .../map/service/MapCustomServiceImpl.java | 24 +-- 5 files changed, 122 insertions(+), 114 deletions(-) diff --git a/src/main/java/com/otakumap/domain/map/controller/MapController.java b/src/main/java/com/otakumap/domain/map/controller/MapController.java index fe6808d8..0362ccbc 100644 --- a/src/main/java/com/otakumap/domain/map/controller/MapController.java +++ b/src/main/java/com/otakumap/domain/map/controller/MapController.java @@ -13,27 +13,27 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -//@RestController -//@RequestMapping("/api/map") -//@RequiredArgsConstructor -//@Validated -//public class MapController { -// -// private final MapCustomService mapCustomService; -// -// @GetMapping("/details") -// @Operation(summary = "지도에서 장소 및 이벤트 정보 보기", description = "장소의 Latitude, Longitude 수령해 해당 장소의 명소와 이벤트를 조회합니다.") -// @ApiResponses({ -// @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), -// }) -// @Parameters({ -// @Parameter(name = "latitude"), -// @Parameter(name = "longitude"), -// }) -// public ApiResponse getSearchedPlaceInfoList( -// @CurrentUser User user, -// @RequestParam Double latitude, -// @RequestParam Double longitude) { -// return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(user, latitude, longitude)); -// } -//} +@RestController +@RequestMapping("/api/map") +@RequiredArgsConstructor +@Validated +public class MapController { + + private final MapCustomService mapCustomService; + + @GetMapping("/details") + @Operation(summary = "지도에서 장소 및 이벤트 정보 보기", description = "장소의 Latitude, Longitude 수령해 해당 장소의 명소와 이벤트를 조회합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"), + }) + @Parameters({ + @Parameter(name = "latitude"), + @Parameter(name = "longitude"), + }) + public ApiResponse getSearchedPlaceInfoList( + @CurrentUser User user, + @RequestParam Double latitude, + @RequestParam Double longitude) { + return ApiResponse.onSuccess(mapCustomService.findAllMapDetails(user, latitude, longitude)); + } +} diff --git a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java index ad70230a..f1d8e7d0 100644 --- a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java +++ b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java @@ -43,7 +43,8 @@ public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( .endDate(event.getEndDate()) .isLiked(isLiked) .locationName(locationName) - .animationName(animation != null ? animation.getName() : "") + .animationId(animation.getId()) + .animationName(animation.getName()) .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) .build(); } @@ -59,7 +60,8 @@ public static MapResponseDTO.MapDetailPlaceDTO toMapDetailPlaceDTO( .name(place.getName()) .detail(place.getDetail()) .isLiked(isLiked) - .animationName(animation != null ? animation.getName() : "") + .animationId(animation.getId()) + .animationName(animation.getName()) .hashtags(hashTags.stream().map(HashTag::getName).collect(Collectors.toList())) .build(); } diff --git a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java index 4e308509..e72565b3 100644 --- a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java +++ b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java @@ -31,6 +31,7 @@ public static class MapDetailEventDTO { private LocalDate endDate; private Boolean isLiked; private String locationName; + private Long animationId; private String animationName; private ImageResponseDTO.ImageDTO thumbnail; private List hashtags; @@ -46,6 +47,7 @@ public static class MapDetailPlaceDTO { private String name; private String detail; private Boolean isLiked; + private Long animationId; private String animationName; private List hashtags; } diff --git a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java index 0c9ddd5d..aa78a841 100644 --- a/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java +++ b/src/main/java/com/otakumap/domain/map/repository/MapRepositoryCustomImpl.java @@ -1,76 +1,80 @@ -//package com.otakumap.domain.map.repository; -// -//import com.otakumap.domain.event.entity.Event; -//import com.otakumap.domain.event.repository.EventRepository; -//import com.otakumap.domain.event_like.repository.EventLikeRepository; -//import com.otakumap.domain.map.converter.MapConverter; -//import com.otakumap.domain.place.entity.Place; -//import com.otakumap.domain.place.repository.PlaceRepository; -//import com.otakumap.domain.map.dto.MapResponseDTO; -//import com.otakumap.domain.animation.entity.Animation; -//import com.otakumap.domain.mapping.PlaceAnimation; -//import com.otakumap.domain.mapping.EventAnimation; -//import com.otakumap.domain.hash_tag.entity.HashTag; -//import com.otakumap.domain.mapping.EventHashTag; -//import com.otakumap.domain.place_like.repository.PlaceLikeRepository; -//import com.otakumap.domain.user.entity.User; -//import lombok.RequiredArgsConstructor; -//import org.springframework.stereotype.Repository; -// -//import java.util.ArrayList; -//import java.util.List; -//import java.util.concurrent.atomic.AtomicReference; -//import java.util.stream.Collectors; -// -//@Repository -//@RequiredArgsConstructor -//public class MapRepositoryCustomImpl implements MapRepositoryCustom { -// -// private final EventRepository eventRepository; -// private final PlaceRepository placeRepository; -// private final EventLikeRepository eventLikeRepository; -// private final PlaceLikeRepository placeLikeRepository; -// -// @Override -// public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { -// List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); -// List eventDTOs = eventList.stream().map(event -> { -// Boolean isLiked = Boolean.FALSE; -// List eventHashTags = event.getEventHashTagList().stream() -// .map(EventHashTag::getHashTag) -// .collect(Collectors.toList()); -// -// List eventAnimations = event.getEventAnimationList().stream() -// .map(EventAnimation::getAnimation) -// .toList(); -// if(user!=null) { -// isLiked = eventLikeRepository.existsByUserAndEvent(user, event); -// } -// return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimations.isEmpty() ? null : eventAnimations.get(0), eventHashTags); -// }).collect(Collectors.toList()); -// -// List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); -// List placeDTOs = placeList.stream().map(place -> { -// List placeHashTags = new ArrayList<>(); -// AtomicReference isLiked = new AtomicReference<>(Boolean.FALSE); -// -// place.getPlaceAnimationList().forEach(placeAnimation -> { -// placeAnimation.getPlaceAnimationHashTags().forEach(hashTag -> { -// placeHashTags.add(hashTag.getHashTag()); -// }); -// -// if(user!=null) { -// isLiked.set(placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation)); -// } -// }); -// -// List placeAnimations = place.getPlaceAnimationList().stream() -// .map(PlaceAnimation::getAnimation) -// .toList(); -// -// return MapConverter.toMapDetailPlaceDTO(place, isLiked.get(), placeAnimations.isEmpty() ? null : placeAnimations.get(0), placeHashTags); -// }).collect(Collectors.toList()); -// -// return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); -// } -//} +package com.otakumap.domain.map.repository; + +import com.otakumap.domain.event.entity.Event; +import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_like.repository.EventLikeRepository; +import com.otakumap.domain.map.converter.MapConverter; +import com.otakumap.domain.mapping.AnimationHashtag; +import com.otakumap.domain.place.entity.Place; +import com.otakumap.domain.place.repository.PlaceRepository; +import com.otakumap.domain.map.dto.MapResponseDTO; +import com.otakumap.domain.animation.entity.Animation; +import com.otakumap.domain.mapping.PlaceAnimation; +import com.otakumap.domain.mapping.EventAnimation; +import com.otakumap.domain.hash_tag.entity.HashTag; +import com.otakumap.domain.place_like.repository.PlaceLikeRepository; +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class MapRepositoryCustomImpl implements MapRepositoryCustom { + + private final EventRepository eventRepository; + private final PlaceRepository placeRepository; + private final EventLikeRepository eventLikeRepository; + private final PlaceLikeRepository placeLikeRepository; + + @Override + public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { + List eventList = eventRepository.findEventsByLocationWithAnimations(latitude, longitude); + List eventDTOs = eventList.stream().map(event -> { + Boolean isLiked = Boolean.FALSE; + + List eventAnimations = event.getEventAnimationList().stream() + .map(EventAnimation::getAnimation) + .toList(); + + Animation eventAnimation = (!eventAnimations.isEmpty() ? eventAnimations.get(0) : null); + List eventHashTags = new ArrayList<>(); + if(eventAnimation != null) { + eventHashTags = eventAnimation.getAnimationHashtags().stream() + .map(AnimationHashtag::getHashTag) + .toList(); + } + + if(user!=null) { + isLiked = eventLikeRepository.existsByUserAndEvent(user, event); + } + return MapConverter.toMapDetailEventDTO(event, isLiked, event.getEventLocation().getName(), eventAnimation, eventHashTags); + }).collect(Collectors.toList()); + + List placeList = placeRepository.findPlacesByLocationWithAnimations(latitude, longitude); + List placeDTOs = placeList.stream().map(place -> { + List placeHashTags = new ArrayList<>(); + AtomicReference isLiked = new AtomicReference<>(Boolean.FALSE); + + place.getPlaceAnimationList().forEach(placeAnimation -> { + placeAnimation.getAnimation().getAnimationHashtags().forEach(hashTag -> placeHashTags.add(hashTag.getHashTag())); + + if(user!=null) { + isLiked.set(placeLikeRepository.existsByUserAndPlaceAnimation(user, placeAnimation)); + } + }); + + List placeAnimations = place.getPlaceAnimationList().stream() + .map(PlaceAnimation::getAnimation) + .toList(); + + return MapConverter.toMapDetailPlaceDTO(place, isLiked.get(), placeAnimations.get(0), placeHashTags); + }).collect(Collectors.toList()); + + return MapConverter.toMapDetailDTO(eventDTOs, placeDTOs); + } +} diff --git a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java index 148e3cd1..b140742b 100644 --- a/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java +++ b/src/main/java/com/otakumap/domain/map/service/MapCustomServiceImpl.java @@ -7,15 +7,15 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -//@Service -//@RequiredArgsConstructor -//@Transactional(readOnly = true) -//public class MapCustomServiceImpl implements MapCustomService { -// -// private final MapRepositoryCustom mapRepositoryCustom; -// -// @Override -// public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { -// return mapRepositoryCustom.findAllMapDetails(user, latitude, longitude); -// } -//} \ No newline at end of file +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MapCustomServiceImpl implements MapCustomService { + + private final MapRepositoryCustom mapRepositoryCustom; + + @Override + public MapResponseDTO.MapDetailDTO findAllMapDetails(User user, Double latitude, Double longitude) { + return mapRepositoryCustom.findAllMapDetails(user, latitude, longitude); + } +} \ No newline at end of file From e501c81ec0aa4b328891ec30012161f8bfd527c2 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 18 Feb 2025 10:47:02 +0900 Subject: [PATCH 445/516] =?UTF-8?q?Refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20url=EB=A7=8C=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/map/converter/MapConverter.java | 5 ++--- .../java/com/otakumap/domain/map/dto/MapResponseDTO.java | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java index f1d8e7d0..61b51648 100644 --- a/src/main/java/com/otakumap/domain/map/converter/MapConverter.java +++ b/src/main/java/com/otakumap/domain/map/converter/MapConverter.java @@ -4,7 +4,6 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.hash_tag.entity.HashTag; import com.otakumap.domain.image.converter.ImageConverter; -import com.otakumap.domain.image.dto.ImageResponseDTO; import com.otakumap.domain.map.dto.MapResponseDTO; import com.otakumap.domain.place.entity.Place; import org.springframework.stereotype.Component; @@ -30,9 +29,9 @@ public static MapResponseDTO.MapDetailEventDTO toMapDetailEventDTO( String locationName, Animation animation, List hashTags) { - ImageResponseDTO.ImageDTO image = null; + String image = ""; if(event.getThumbnailImage() != null) { - image = ImageConverter.toImageDTO(event.getThumbnailImage()); + image = ImageConverter.toImageDTO(event.getThumbnailImage()).getFileUrl(); } return MapResponseDTO.MapDetailEventDTO.builder() diff --git a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java index e72565b3..cc0db685 100644 --- a/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java +++ b/src/main/java/com/otakumap/domain/map/dto/MapResponseDTO.java @@ -1,6 +1,5 @@ package com.otakumap.domain.map.dto; -import com.otakumap.domain.image.dto.ImageResponseDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -33,7 +32,7 @@ public static class MapDetailEventDTO { private String locationName; private Long animationId; private String animationName; - private ImageResponseDTO.ImageDTO thumbnail; + private String thumbnail; private List hashtags; } From eb08983421acb43e1c5a6f0cd4a211c5edb8ece2 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Tue, 18 Feb 2025 11:27:15 +0900 Subject: [PATCH 446/516] =?UTF-8?q?Feat:=20=EA=B5=AC=EB=A7=A4=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20entity=20=EB=B0=8F=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 4 ++ .../place_review/entity/PlaceReview.java | 5 ++ .../otakumap/domain/point/entity/Point.java | 6 +++ .../transaction/entity/Transaction.java | 46 +++++++++++++++++++ .../transaction/enums/TransactionType.java | 5 ++ 5 files changed, 66 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/transaction/entity/Transaction.java create mode 100644 src/main/java/com/otakumap/domain/transaction/enums/TransactionType.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index c35464ea..b5b80244 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -5,6 +5,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -58,6 +59,9 @@ public class EventReview extends BaseEntity { @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; + @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL) + private List transactionList = new ArrayList<>(); + public void setPlaceList(List placeList) { this.placeList = placeList; } public void setAnimation(Animation animation) { this.animation = animation; } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 315cf99c..40b9b5ae 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -4,6 +4,7 @@ import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.mapping.PlaceReviewPlace; import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -53,6 +54,10 @@ public class PlaceReview extends BaseEntity { @JoinColumn(name = "route_id", referencedColumnName = "id") private Route route; + @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL) + private List transactionList = new ArrayList<>(); + + public void setPlaceList(List placeList) { this.placeList = placeList; } public void setAnimation(Animation animation) { this.animation = animation; } diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 2b1f85f3..65ec2995 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -2,6 +2,7 @@ import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -12,6 +13,8 @@ import org.hibernate.annotations.CreationTimestamp; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @Getter @@ -48,4 +51,7 @@ public class Point extends BaseEntity { @Enumerated(EnumType.STRING) @Column(nullable = false) private PaymentStatus status = PaymentStatus.PENDING; + + @OneToMany(mappedBy = "point", cascade = CascadeType.ALL) + private List transactionList = new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java new file mode 100644 index 00000000..0d04ae60 --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java @@ -0,0 +1,46 @@ +package com.otakumap.domain.transaction.entity; + + +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.transaction.enums.TransactionType; +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "transaction") +public class Transaction extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "point_id", referencedColumnName = "id", nullable = false) + private Point point; + + @Column(name = "type", nullable = false) + private TransactionType type; + + @Column(name = "amount", nullable = false) + private int amount; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_review_id", referencedColumnName = "id") + private EventReview eventReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_review_id", referencedColumnName = "id") + private PlaceReview placeReview; + + +} diff --git a/src/main/java/com/otakumap/domain/transaction/enums/TransactionType.java b/src/main/java/com/otakumap/domain/transaction/enums/TransactionType.java new file mode 100644 index 00000000..ea4620c4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/enums/TransactionType.java @@ -0,0 +1,5 @@ +package com.otakumap.domain.transaction.enums; + +public enum TransactionType { + USAGE, EARNING +} From 2d20c2e2ce7b53ab1d3e9dd685de1a641b0cdfa5 Mon Sep 17 00:00:00 2001 From: mk-star Date: Tue, 18 Feb 2025 14:11:13 +0900 Subject: [PATCH 447/516] =?UTF-8?q?Feat:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=EB=A1=9C=20=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/auth/service/SocialAuthServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java b/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java index 0a56ec00..5747b375 100644 --- a/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java +++ b/src/main/java/com/otakumap/domain/auth/service/SocialAuthServiceImpl.java @@ -6,6 +6,8 @@ import com.otakumap.domain.auth.dto.*; import com.otakumap.domain.auth.jwt.userdetails.PrincipalDetails; import com.otakumap.domain.auth.jwt.util.JwtProvider; +import com.otakumap.domain.image.entity.Image; +import com.otakumap.domain.image.repository.ImageRepository; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; @@ -31,6 +33,7 @@ public class SocialAuthServiceImpl implements SocialAuthService { private final SocialProperties socialProperties; private final RestTemplate restTemplate; private final UserRepository userRepository; + private final ImageRepository imageRepository; private final JwtProvider jwtProvider; private final Gson gson; @@ -127,9 +130,12 @@ private T getUserInfo(String accessToken, String userInfoUri, HttpMethod htt private AuthResponseDTO.LoginResultDTO socialLogin(String provider, T userInfo){ Optional userOptional = userRepository.findByEmail(getEmail(provider, userInfo)); User user; + // 기본 이미지는 항상 PK값을 1로 가져오도록 설정 + Image profileImage = imageRepository.findById(1L).orElseThrow(() -> new AuthHandler(ErrorStatus.IMAGE_NOT_FOUND)); if (userOptional.isEmpty()) { // 회원가입 user = createUser(provider, userInfo); + user.setProfileImage(profileImage); userRepository.save(user); } else { user = userOptional.get(); From 838ee512771014141cf82ab883b830b346a498d4 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 18 Feb 2025 22:52:05 +0900 Subject: [PATCH 448/516] =?UTF-8?q?feature:=20=EA=B2=B0=EC=A0=9C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 +++- .../payment/controller/PaymentController.java | 46 +++++++++++++++ .../payment/dto/PaymentVerifyRequest.java | 16 ++++++ .../service/PaymentCommandService.java | 13 +++++ .../service/PaymentCommandServiceImpl.java | 56 +++++++++++++++++++ .../point/controller/PointController.java | 38 +++++++++++++ .../otakumap/domain/point/entity/Point.java | 13 ++++- .../point/repository/PointRepository.java | 9 +++ .../transaction/entity/Transaction.java | 3 - .../apiPayload/code/status/ErrorStatus.java | 8 ++- .../exception/handler/PaymentHandler.java | 10 ++++ .../otakumap/global/config/IamportConfig.java | 22 ++++++++ 12 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/payment/controller/PaymentController.java create mode 100644 src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java create mode 100644 src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java create mode 100644 src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java create mode 100644 src/main/java/com/otakumap/domain/point/controller/PointController.java create mode 100644 src/main/java/com/otakumap/domain/point/repository/PointRepository.java create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/PaymentHandler.java create mode 100644 src/main/java/com/otakumap/global/config/IamportConfig.java diff --git a/build.gradle b/build.gradle index ebcf7408..d85dac31 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ configurations { repositories { mavenCentral() + maven { url 'https://jitpack.io' } } jar{ @@ -43,7 +44,7 @@ dependencies { // security 관련 implementation 'org.springframework.boot:spring-boot-starter-security' - // + // implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' // 이메일 인증 기능 @@ -67,12 +68,19 @@ dependencies { implementation 'com.google.code.gson:gson' // 구글 places api 관련 -// implementation 'com.google.maps:google-maps-services:0.42.2' + //implementation 'com.google.maps:google-maps-services:0.42.2' // Jackson: JSON 응답을 파싱 implementation 'com.fasterxml.jackson.core:jackson-databind' // AWS S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // iamport 결제 관련 + implementation group: 'com.github.iamport', name: 'iamport-rest-client-java', version: '0.2.22' + implementation group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.9.0' + //implementation group: 'com.google.code.gson', name: 'gson', version: '2.11.0' + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3' + implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.3.0' } tasks.named('test') { diff --git a/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java b/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java new file mode 100644 index 00000000..dd354bef --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java @@ -0,0 +1,46 @@ +package com.otakumap.domain.payment.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.payment.dto.PaymentVerifyRequest; +import com.otakumap.domain.payment.service.PaymentCommandService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import com.siot.IamportRestClient.exception.IamportResponseException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/payments") +@RequiredArgsConstructor +@Validated +@Slf4j +public class PaymentController { + + private final PaymentCommandService paymentCommandService; + + @Operation(summary = "결제 검증", description = "결제가 제대로 진행됐는지 검증합니다.") + @PostMapping("/verify") + @Parameters({ + @Parameter(name = "imp_uid", description = "IAMPORT 결제 고유 ID"), + @Parameter(name = "merchant_uid", description = "주문 ID"), + @Parameter(name = "amount", description = "결제 금액") + }) + public ApiResponse verifyPayment( + @Valid @RequestBody PaymentVerifyRequest request, + @CurrentUser User user) throws IamportResponseException, IOException { + + // 결제 검증 서비스 호출 + paymentCommandService.verifyPayment(user, request); + + // 성공적인 응답 반환 + return ApiResponse.onSuccess("결제가 검증되었습니다."); + } +} diff --git a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java new file mode 100644 index 00000000..8c2785a5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java @@ -0,0 +1,16 @@ +package com.otakumap.domain.payment.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +public class PaymentVerifyRequest { + @NotNull(message = "imp_uid는 필수입니다.") + private String impUid; + + @NotNull(message = "merchant_uid는 필수입니다.") + private String merchantUid; + + @NotNull(message = "amount는 필수입니다.") + private Long amount; +} diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java new file mode 100644 index 00000000..fc729bd4 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java @@ -0,0 +1,13 @@ +package com.otakumap.domain.payment.service; + +import com.otakumap.domain.payment.dto.PaymentVerifyRequest; +import com.otakumap.domain.user.entity.User; +import com.siot.IamportRestClient.exception.IamportResponseException; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public interface PaymentCommandService { + void verifyPayment(User user, PaymentVerifyRequest request) throws IamportResponseException, IOException; +} diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java new file mode 100644 index 00000000..cbcb3a3b --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -0,0 +1,56 @@ +package com.otakumap.domain.payment.service; + +import com.otakumap.domain.payment.dto.PaymentVerifyRequest; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.repository.PointRepository; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PaymentHandler; +import com.siot.IamportRestClient.exception.IamportResponseException; +import com.siot.IamportRestClient.response.IamportResponse; +import com.siot.IamportRestClient.response.Payment; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.siot.IamportRestClient.IamportClient; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +@Transactional +public class PaymentCommandServiceImpl implements PaymentCommandService { + + private final IamportClient iamportClient; + private final PointRepository pointRepository; + + @Transactional + public void verifyPayment(User user, PaymentVerifyRequest request) throws IOException, IamportResponseException { + // 1. 아임포트 결제 정보 조회 + IamportResponse paymentResponse = iamportClient.paymentByImpUid(request.getImpUid()); + Payment payment = paymentResponse.getResponse(); + + if (payment == null) { + throw new PaymentHandler(ErrorStatus.PAYMENT_NOT_FOUND); + } + + // 2. 결제 상태 확인 + if (!"paid".equals(payment.getStatus())) { + throw new PaymentHandler(ErrorStatus.PAYMENT_STATUS_INVALID); + } + + // 3. 결제 금액 검증 + if (!payment.getAmount().equals(request.getAmount())) { + throw new PaymentHandler(ErrorStatus.PAYMENT_AMOUNT_MISMATCH); + } + + // 4. 중복 결제 방지 + if (pointRepository.findByMerchantUid(request.getMerchantUid()).isPresent()) { + throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); + } + + // 5. 포인트 저장 + Point point = new Point(user, request.getMerchantUid(), request.getAmount()); + pointRepository.save(point); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java new file mode 100644 index 00000000..9fa5ecf2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -0,0 +1,38 @@ +package com.otakumap.domain.point.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/points") +@RequiredArgsConstructor +@Validated +public class PointController { + + //private final PointCommandService pointCommandService; + + @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") + @PostMapping("/charge") + @Parameters({ + @Parameter(name = "point", description = "충전할 포인트") + }) + public ApiResponse chargePoints(@RequestParam Long point, @CurrentUser User user) { + // 포인트 충전 서비스 호출 + //Point pointRecord = pointCommandService.chargePoints(user, point); + + // 성공적인 응답 반환 + //return ApiResponse.onSuccess("충전 성공하였습니다.", pointRecord); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 65ec2995..a419dd88 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -1,6 +1,5 @@ package com.otakumap.domain.point.entity; - import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.user.entity.User; @@ -28,23 +27,29 @@ public class Point extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + // 충전 받는 사람 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + // 충전된 포인트 금액 @Column(nullable = false) private Long point; + // 충전된 시간 @Column(name = "charged_at", nullable = false, updatable = false) @CreationTimestamp private LocalDateTime chargedAt; + // 충전한 사람 @Column(name = "charged_by") private String chargedBy; + // 주문 ID @Column(name = "merchant_uid", unique = true, nullable = false) private String merchantUid; + // Iamport 결제 고유 ID @Column(name = "imp_uid") private String impUid; @@ -54,4 +59,10 @@ public class Point extends BaseEntity { @OneToMany(mappedBy = "point", cascade = CascadeType.ALL) private List transactionList = new ArrayList<>(); + + public Point(User user, String merchantUid, Long point) { + this.user = user; + this.merchantUid = merchantUid; + this.point = point; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java new file mode 100644 index 00000000..147f550d --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.point.repository; + +import com.otakumap.domain.point.entity.Point; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +public interface PointRepository extends JpaRepository { + Optional findByMerchantUid(String merchantUid); +} diff --git a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java index 0d04ae60..e9c1cacc 100644 --- a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java +++ b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java @@ -1,6 +1,5 @@ package com.otakumap.domain.transaction.entity; - import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.point.entity.Point; @@ -41,6 +40,4 @@ public class Transaction extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_review_id", referencedColumnName = "id") private PlaceReview placeReview; - - } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 71fe191d..5e3bbcb8 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -106,7 +106,13 @@ public enum ErrorStatus implements BaseErrorCode { // 한 줄 리뷰 관련 에러 EVENT_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4008", "존재하지 않는 이벤트 한 줄 리뷰입니다."), - PLACE_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4006", "존재하지 않는 이벤트 한 줄 리뷰입니다."),; + PLACE_SHORT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4006", "존재하지 않는 이벤트 한 줄 리뷰입니다."), + + // 결제 관련 에러 + PAYMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "PAYMENT4001", "결제 정보를 찾을 수 없습니다."), + PAYMENT_STATUS_INVALID(HttpStatus.BAD_REQUEST, "PAYMENT4002", "결제 상태가 유효하지 않습니다."), + PAYMENT_AMOUNT_MISMATCH(HttpStatus.BAD_REQUEST, "PAYMENT4003", "결제 금액이 일치하지 않습니다."), + PAYMENT_DUPLICATE(HttpStatus.BAD_REQUEST, "PAYMENT4004", "이미 처리된 결제입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/PaymentHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/PaymentHandler.java new file mode 100644 index 00000000..5886fb57 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/PaymentHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class PaymentHandler extends GeneralException { + public PaymentHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/otakumap/global/config/IamportConfig.java b/src/main/java/com/otakumap/global/config/IamportConfig.java new file mode 100644 index 00000000..36b4abdf --- /dev/null +++ b/src/main/java/com/otakumap/global/config/IamportConfig.java @@ -0,0 +1,22 @@ +package com.otakumap.global.config; + +import com.siot.IamportRestClient.IamportClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class IamportConfig { + + + @Value("${imp-api-key}") + private String apiKey; + + @Value("${imp-api-secretKey}") + private String apiSecret; + + @Bean + public IamportClient iamportClient() { + return new IamportClient(apiKey, apiSecret); + } +} From 9361ecb62dd05016d5b379fac39d90cb37dde8bb Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Tue, 18 Feb 2025 23:51:45 +0900 Subject: [PATCH 449/516] =?UTF-8?q?feature:=20override=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EC=96=B4=EC=9E=88=EC=96=B4=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/place_like/service/PlaceLikeQueryService.java | 4 ++++ .../domain/place_like/service/PlaceLikeQueryServiceImpl.java | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java index a3f4a580..e16d0399 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryService.java @@ -1,10 +1,14 @@ package com.otakumap.domain.place_like.service; import com.otakumap.domain.place_like.dto.PlaceLikeResponseDTO; +import com.otakumap.domain.place_like.entity.PlaceLike; import com.otakumap.domain.user.entity.User; +import java.util.List; + public interface PlaceLikeQueryService { PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, Boolean isFavorite, Long lastId, int limit); + PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(List placeLikes, int limit); boolean isPlaceLikeExist(Long id); PlaceLikeResponseDTO.PlaceLikeDetailDTO getPlaceLike(Long placeLikeId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java index e3ae44cb..4412ad2c 100644 --- a/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_like/service/PlaceLikeQueryServiceImpl.java @@ -49,7 +49,8 @@ public PlaceLikeResponseDTO.PlaceLikePreViewListDTO getPlaceLikeList(User user, return createPlaceLikePreviewListDTO(result, limit); } - private PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(List placeLikes, int limit) { + @Override + public PlaceLikeResponseDTO.PlaceLikePreViewListDTO createPlaceLikePreviewListDTO(List placeLikes, int limit) { boolean hasNext = placeLikes.size() > limit; Long lastId = null; From 0dcfa0dd5771436bc9c4f163d3cc0b0149d304ea Mon Sep 17 00:00:00 2001 From: mk-star Date: Wed, 19 Feb 2025 00:32:50 +0900 Subject: [PATCH 450/516] =?UTF-8?q?Feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=A9=EC=A0=84=20=EB=82=B4=EC=97=AD=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/controller/PointController.java | 11 +++++- .../point/converter/PointConverter.java | 31 +++++++++++++++++ .../domain/point/dto/PointResponseDTO.java | 34 +++++++++++++++++++ .../point/repository/PointRepository.java | 4 +++ .../point/service/PointQueryservice.java | 9 +++++ .../point/service/PointQueryserviceImpl.java | 20 +++++++++++ 6 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/point/converter/PointConverter.java create mode 100644 src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/point/service/PointQueryservice.java create mode 100644 src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 9fa5ecf2..014814b8 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -1,7 +1,10 @@ package com.otakumap.domain.point.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.point.converter.PointConverter; +import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.service.PointQueryservice; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -19,8 +22,8 @@ @RequiredArgsConstructor @Validated public class PointController { - //private final PointCommandService pointCommandService; + private final PointQueryservice pointQueryservice; @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") @@ -35,4 +38,10 @@ public ApiResponse chargePoints(@RequestParam Long point, @CurrentUser U //return ApiResponse.onSuccess("충전 성공하였습니다.", pointRecord); return null; } + + @Operation(summary = "포인트 충전 내역 확인", description = "포인트 충전 내역을 확인합니다. page는 1부터 시작합니다.") + @PostMapping("/transactions/charges") + public ApiResponse getChargePointList(@CurrentUser User user, @RequestParam(name = "page") Integer page) { + return ApiResponse.onSuccess(PointConverter.pointPreViewListDTO(pointQueryservice.getChargePointList(user, page))); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java new file mode 100644 index 00000000..6e38ddb9 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -0,0 +1,31 @@ +package com.otakumap.domain.point.converter; + +import com.otakumap.domain.point.dto.PointResponseDTO; +import com.otakumap.domain.point.entity.Point; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + +public class PointConverter { + public static PointResponseDTO.PointPreViewDTO pointPreViewDTO(Point point){ + return PointResponseDTO.PointPreViewDTO.builder() + .chargedBy(point.getChargedBy()) + .point(point.getPoint()) + .chargedAt(point.getChargedAt()) + .build(); + } + public static PointResponseDTO.PointPreViewListDTO pointPreViewListDTO(Page pointList) { + List pointPreViewDTOList = pointList.stream() + .map(PointConverter::pointPreViewDTO).collect(Collectors.toList()); + + return PointResponseDTO.PointPreViewListDTO.builder() + .isLast(pointList.isLast()) + .isFirst(pointList.isFirst()) + .totalPage(pointList.getTotalPages()) + .totalElements(pointList.getTotalElements()) + .listSize(pointPreViewDTOList.size()) + .pointList(pointPreViewDTOList) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java b/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java new file mode 100644 index 00000000..c36d063e --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java @@ -0,0 +1,34 @@ +package com.otakumap.domain.point.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class PointResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PointPreViewListDTO { + List pointList; + Integer listSize; + Integer totalPage; + Long totalElements; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PointPreViewDTO { + String chargedBy; + Long point; + LocalDateTime chargedAt; + } +} diff --git a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java index 147f550d..7a564b4c 100644 --- a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java +++ b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java @@ -1,9 +1,13 @@ package com.otakumap.domain.point.repository; import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface PointRepository extends JpaRepository { Optional findByMerchantUid(String merchantUid); + Page findAllByUser(User user, PageRequest pageRequest); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java b/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java new file mode 100644 index 00000000..82f0216e --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.point.service; + +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Page; + +public interface PointQueryservice { + Page getChargePointList(User user, Integer page); +} diff --git a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java new file mode 100644 index 00000000..2f0cad14 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java @@ -0,0 +1,20 @@ +package com.otakumap.domain.point.service; + +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.repository.PointRepository; +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PointQueryserviceImpl implements PointQueryservice { + private final PointRepository pointRepository; + + @Override + public Page getChargePointList(User user, Integer page) { + return pointRepository.findAllByUser(user, PageRequest.of(page - 1, 6)); + } +} \ No newline at end of file From b30691edc161a4f3f923409c29847f51ddcab136 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 00:44:39 +0900 Subject: [PATCH 451/516] =?UTF-8?q?feature:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=A9=EC=A0=84=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/repository/PaymentRepository.java | 10 +++++ .../point/controller/PointController.java | 27 +++++------- .../domain/point/dto/PointRequestDTO.java | 12 ++++++ .../point/service/PointCommandService.java | 7 +++ .../service/PointCommandServiceImpl.java | 43 +++++++++++++++++++ 5 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java create mode 100644 src/main/java/com/otakumap/domain/point/dto/PointRequestDTO.java create mode 100644 src/main/java/com/otakumap/domain/point/service/PointCommandService.java create mode 100644 src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java b/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java new file mode 100644 index 00000000..f3d49e38 --- /dev/null +++ b/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.payment.repository; + +import com.otakumap.domain.payment.entity.Payment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PaymentRepository extends JpaRepository { + boolean existsByMerchantUid(String merchantUid); +} diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 9fa5ecf2..64735f61 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -1,18 +1,17 @@ package com.otakumap.domain.point.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.dto.PointRequestDTO; +import com.otakumap.domain.point.service.PointCommandService; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -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.*; @RestController @RequestMapping("/api/points") @@ -20,19 +19,15 @@ @Validated public class PointController { - //private final PointCommandService pointCommandService; + private final PointCommandService pointCommandService; @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") - @Parameters({ - @Parameter(name = "point", description = "충전할 포인트") - }) - public ApiResponse chargePoints(@RequestParam Long point, @CurrentUser User user) { - // 포인트 충전 서비스 호출 - //Point pointRecord = pointCommandService.chargePoints(user, point); - - // 성공적인 응답 반환 - //return ApiResponse.onSuccess("충전 성공하였습니다.", pointRecord); - return null; + public ApiResponse chargePoints( + @RequestBody @Valid PointRequestDTO.PointChargeDTO request, + @CurrentUser User user + ) { + pointCommandService.chargePoints(user, request.getPoint()); + return ApiResponse.onSuccess("충전 성공하였습니다."); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/dto/PointRequestDTO.java b/src/main/java/com/otakumap/domain/point/dto/PointRequestDTO.java new file mode 100644 index 00000000..8691c3ba --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/dto/PointRequestDTO.java @@ -0,0 +1,12 @@ +package com.otakumap.domain.point.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class PointRequestDTO { + @Getter + public static class PointChargeDTO { + @NotNull(message = "충전할 point 입력은 필수입니다.") + Long point; + } +} diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java new file mode 100644 index 00000000..0e3dce75 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.point.service; + +import com.otakumap.domain.user.entity.User; + +public interface PointCommandService { + void chargePoints(User user, Long point); +} diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java new file mode 100644 index 00000000..56c859c5 --- /dev/null +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java @@ -0,0 +1,43 @@ +package com.otakumap.domain.point.service; + +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.payment.repository.PaymentRepository; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.repository.PointRepository; +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Transactional +public class PointCommandServiceImpl implements PointCommandService { + + private final PaymentRepository paymentRepository; + private final PointRepository pointRepository; + + @Override + public void chargePoints(User user, Long point) { + // 랜덤 merchantUid 생성 (중복 방지) + String merchantUid; + do { + merchantUid = UUID.randomUUID().toString(); + } while (paymentRepository.existsByMerchantUid(merchantUid)); + + // 포인트 충전 내역 저장 + Point pointRecord = Point.builder() + .user(user) + .point(point) + .merchantUid(merchantUid) + .chargedBy(user.getName()) + .chargedAt(LocalDateTime.now()) + .status(PaymentStatus.PAID) + .build(); + + pointRepository.save(pointRecord); + } +} From 5e229b2bfeb081c468969c60cf29efb58355c11b Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 00:45:18 +0900 Subject: [PATCH 452/516] =?UTF-8?q?Chore:=20unused=20import=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/point/controller/PointController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 64735f61..fc3a2742 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -6,8 +6,6 @@ import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; From b817597e41a3e860f89fc4085bf9397be629e4bc Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 01:05:13 +0900 Subject: [PATCH 453/516] =?UTF-8?q?Feat:=20=EA=B1=B0=EB=9E=98=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TransactionController.java | 51 +++++++++++++++++++ .../converter/TransactionConverter.java | 33 ++++++++++++ .../dto/TransactionResponseDTO.java | 33 ++++++++++++ .../transaction/entity/Transaction.java | 3 +- .../repository/TransactionRepository.java | 13 +++++ .../service/TransactionQueryService.java | 9 ++++ .../service/TransactionQueryServiceImpl.java | 39 ++++++++++++++ 7 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java create mode 100644 src/main/java/com/otakumap/domain/transaction/converter/TransactionConverter.java create mode 100644 src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java create mode 100644 src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java create mode 100644 src/main/java/com/otakumap/domain/transaction/service/TransactionQueryService.java create mode 100644 src/main/java/com/otakumap/domain/transaction/service/TransactionQueryServiceImpl.java diff --git a/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java b/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java new file mode 100644 index 00000000..0233d1f2 --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java @@ -0,0 +1,51 @@ +package com.otakumap.domain.transaction.controller; + + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.transaction.dto.TransactionResponseDTO; +import com.otakumap.domain.transaction.service.TransactionQueryService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/transactions") +@RequiredArgsConstructor +@Validated +public class TransactionController { + + private final TransactionQueryService transactionQueryService; + + @Operation(summary = "구매 내역 확인", description = "구매 내역을 조회합니다.") + @PostMapping("/usages") + @Parameters({ + @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0"), + @Parameter(name = "size", description = "한 페이지당 내역 수", example = "5"), + }) + public ApiResponse getUsageTransactions(@CurrentUser User user, + @RequestParam(name = "page") Integer page, + @RequestParam(name = "size") Integer size) { + return ApiResponse.onSuccess(transactionQueryService.getUsageTransactions(user, page, size)); + } + + @Operation(summary = "수익 내역 확인", description = "수익 내역을 조회합니다.") + @PostMapping("/earnings") + @Parameters({ + @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0"), + @Parameter(name = "size", description = "한 페이지당 불러올 수익 내역 수", example = "5"), + }) + public ApiResponse getEarningTransactions(@CurrentUser User user, + @RequestParam(name = "page") Integer page, + @RequestParam(name = "size") Integer size) { + + return ApiResponse.onSuccess(transactionQueryService.getEarningTransactions(user, page, size)); + } +} diff --git a/src/main/java/com/otakumap/domain/transaction/converter/TransactionConverter.java b/src/main/java/com/otakumap/domain/transaction/converter/TransactionConverter.java new file mode 100644 index 00000000..f8bc6991 --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/converter/TransactionConverter.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.transaction.converter; + +import com.otakumap.domain.transaction.dto.TransactionResponseDTO; +import com.otakumap.domain.transaction.entity.Transaction; +import org.springframework.data.domain.Page; + +public class TransactionConverter { + + public static TransactionResponseDTO.TransactionDTO toTransactionDTO(Transaction transaction) { + String title; + if(transaction.getEventReview() == null) { + title = transaction.getPlaceReview().getTitle(); + } else { + title = transaction.getEventReview().getTitle(); + } + + return TransactionResponseDTO.TransactionDTO.builder() + .title(title) + .point(transaction.getAmount()) + .purchasedAt(transaction.getCreatedAt()) + .build(); + } + + public static TransactionResponseDTO.TransactionListDTO toTransactionListDTO(Page transactions) { + return TransactionResponseDTO.TransactionListDTO.builder() + .transactions(transactions.getContent()) + .totalPages(transactions.getTotalPages()) + .totalElements(transactions.getNumber()) + .isLast(transactions.isLast()) + .build(); + + } +} diff --git a/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java b/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java new file mode 100644 index 00000000..128741ed --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java @@ -0,0 +1,33 @@ +package com.otakumap.domain.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class TransactionResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class TransactionDTO { + private String title; + private Integer point; + private LocalDateTime purchasedAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class TransactionListDTO { + private List transactions; + private Integer currentPage; + private Integer totalPages; + private Integer totalElements; + private Boolean isLast; + } +} diff --git a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java index e9c1cacc..81356cee 100644 --- a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java +++ b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java @@ -27,7 +27,8 @@ public class Transaction extends BaseEntity { @JoinColumn(name = "point_id", referencedColumnName = "id", nullable = false) private Point point; - @Column(name = "type", nullable = false) + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") private TransactionType type; @Column(name = "amount", nullable = false) diff --git a/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java b/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java new file mode 100644 index 00000000..cd806523 --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java @@ -0,0 +1,13 @@ +package com.otakumap.domain.transaction.repository; + +import com.otakumap.domain.transaction.entity.Transaction; +import com.otakumap.domain.transaction.enums.TransactionType; +import com.otakumap.domain.user.entity.User; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TransactionRepository extends JpaRepository{ + List findAllByPoint_UserAndType(User user, TransactionType type, Pageable pageRequest); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/transaction/service/TransactionQueryService.java b/src/main/java/com/otakumap/domain/transaction/service/TransactionQueryService.java new file mode 100644 index 00000000..a716559f --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/service/TransactionQueryService.java @@ -0,0 +1,9 @@ +package com.otakumap.domain.transaction.service; + +import com.otakumap.domain.transaction.dto.TransactionResponseDTO; +import com.otakumap.domain.user.entity.User; + +public interface TransactionQueryService { + TransactionResponseDTO.TransactionListDTO getUsageTransactions(User user, Integer page, Integer size); + TransactionResponseDTO.TransactionListDTO getEarningTransactions(User user, Integer page, Integer size); +} diff --git a/src/main/java/com/otakumap/domain/transaction/service/TransactionQueryServiceImpl.java b/src/main/java/com/otakumap/domain/transaction/service/TransactionQueryServiceImpl.java new file mode 100644 index 00000000..6e85ac7d --- /dev/null +++ b/src/main/java/com/otakumap/domain/transaction/service/TransactionQueryServiceImpl.java @@ -0,0 +1,39 @@ +package com.otakumap.domain.transaction.service; + +import com.otakumap.domain.transaction.converter.TransactionConverter; +import com.otakumap.domain.transaction.dto.TransactionResponseDTO; +import com.otakumap.domain.transaction.enums.TransactionType; +import com.otakumap.domain.transaction.repository.TransactionRepository; +import com.otakumap.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class TransactionQueryServiceImpl implements TransactionQueryService { + private final TransactionRepository transactionRepository; + + @Override + public TransactionResponseDTO.TransactionListDTO getUsageTransactions(User user, Integer page, Integer size) { + List transactions = transactionRepository.findAllByPoint_UserAndType(user, TransactionType.USAGE, PageRequest.of(page, size)).stream() + .map(TransactionConverter::toTransactionDTO) + .toList(); + + return TransactionConverter.toTransactionListDTO(new PageImpl<>(transactions, PageRequest.of(page, size), transactions.size())); + } + + @Override + public TransactionResponseDTO.TransactionListDTO getEarningTransactions(User user, Integer page, Integer size) { + List transactions = transactionRepository.findAllByPoint_UserAndType(user, TransactionType.EARNING, PageRequest.of(page, size)).stream() + .map(TransactionConverter::toTransactionDTO) + .toList(); + + return TransactionConverter.toTransactionListDTO(new PageImpl<>(transactions, PageRequest.of(page, size), transactions.size())); + } +} From 5e2d518c2034024a41ed8e0cfbb84834847da7ca Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 01:26:09 +0900 Subject: [PATCH 454/516] =?UTF-8?q?Feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=94=EC=95=A1=20=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/point/controller/PointController.java | 6 ++++++ .../domain/point/converter/PointConverter.java | 7 +++++++ .../otakumap/domain/point/dto/PointResponseDTO.java | 10 ++++++++++ .../domain/point/repository/PointRepository.java | 1 + .../domain/point/service/PointQueryservice.java | 2 ++ .../domain/point/service/PointQueryserviceImpl.java | 7 +++++++ 6 files changed, 33 insertions(+) diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 014814b8..9f94764c 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -44,4 +44,10 @@ public ApiResponse chargePoints(@RequestParam Long point, @CurrentUser U public ApiResponse getChargePointList(@CurrentUser User user, @RequestParam(name = "page") Integer page) { return ApiResponse.onSuccess(PointConverter.pointPreViewListDTO(pointQueryservice.getChargePointList(user, page))); } + + @Operation(summary = "현재 포인트 조회", description = "현재 포인트를 조회합니다.") + @PostMapping("/balance") + public ApiResponse getCurrentPointBalance(@CurrentUser User user) { + return ApiResponse.onSuccess(pointQueryservice.getCurrentPoint(user)); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java index 6e38ddb9..caf52042 100644 --- a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -28,4 +28,11 @@ public static PointResponseDTO.PointPreViewListDTO pointPreViewListDTO(Page { Optional findByMerchantUid(String merchantUid); Page findAllByUser(User user, PageRequest pageRequest); + Point findTopByUserOrderByCreatedAtDesc(User user); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java b/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java index 82f0216e..768510a8 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java +++ b/src/main/java/com/otakumap/domain/point/service/PointQueryservice.java @@ -1,9 +1,11 @@ package com.otakumap.domain.point.service; +import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; public interface PointQueryservice { Page getChargePointList(User user, Integer page); + PointResponseDTO.CurrentPointDTO getCurrentPoint(User user); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java index 2f0cad14..2094fba0 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java +++ b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java @@ -1,5 +1,7 @@ package com.otakumap.domain.point.service; +import com.otakumap.domain.point.converter.PointConverter; +import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.point.repository.PointRepository; import com.otakumap.domain.user.entity.User; @@ -17,4 +19,9 @@ public class PointQueryserviceImpl implements PointQueryservice { public Page getChargePointList(User user, Integer page) { return pointRepository.findAllByUser(user, PageRequest.of(page - 1, 6)); } + + @Override + public PointResponseDTO.CurrentPointDTO getCurrentPoint(User user) { + return PointConverter.toCurrentPointDTO(pointRepository.findTopByUserOrderByCreatedAtDesc(user)); + } } \ No newline at end of file From 10cac211016a2a30f7d01378538fe0f66bd6258e Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 01:27:24 +0900 Subject: [PATCH 455/516] =?UTF-8?q?Refactor:=20API=20method=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaction/controller/TransactionController.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java b/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java index 0233d1f2..cd435ebf 100644 --- a/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java +++ b/src/main/java/com/otakumap/domain/transaction/controller/TransactionController.java @@ -11,10 +11,7 @@ import io.swagger.v3.oas.annotations.Parameters; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -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.*; @RestController @RequestMapping("/api/transactions") @@ -25,7 +22,7 @@ public class TransactionController { private final TransactionQueryService transactionQueryService; @Operation(summary = "구매 내역 확인", description = "구매 내역을 조회합니다.") - @PostMapping("/usages") + @GetMapping("/usages") @Parameters({ @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0"), @Parameter(name = "size", description = "한 페이지당 내역 수", example = "5"), @@ -37,7 +34,7 @@ public ApiResponse getUsageTransactio } @Operation(summary = "수익 내역 확인", description = "수익 내역을 조회합니다.") - @PostMapping("/earnings") + @GetMapping("/earnings") @Parameters({ @Parameter(name = "page", description = "페이지 번호입니다. 0부터 시작합니다.", example = "0"), @Parameter(name = "size", description = "한 페이지당 불러올 수익 내역 수", example = "5"), From 6886bb4935b9b8505b22287d0550e80cee13dcb1 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 01:28:46 +0900 Subject: [PATCH 456/516] =?UTF-8?q?Refactor:=20API=20method=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/point/controller/PointController.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 9f94764c..873b98ff 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -12,10 +12,7 @@ import io.swagger.v3.oas.annotations.Parameters; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -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.*; @RestController @RequestMapping("/api/points") @@ -46,7 +43,7 @@ public ApiResponse getChargePointList(@Cur } @Operation(summary = "현재 포인트 조회", description = "현재 포인트를 조회합니다.") - @PostMapping("/balance") + @GetMapping("/balance") public ApiResponse getCurrentPointBalance(@CurrentUser User user) { return ApiResponse.onSuccess(pointQueryservice.getCurrentPoint(user)); } From 42a2fbf3ce1c8e5fb5b83482a3e05fcf1387d509 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 01:31:19 +0900 Subject: [PATCH 457/516] =?UTF-8?q?Feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=A9=EC=A0=84=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/controller/PointController.java | 35 ++++++++----------- .../point/converter/PointConverter.java | 14 ++++++++ .../service/PointCommandServiceImpl.java | 13 ++----- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 014814b8..42e97eb1 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -2,46 +2,39 @@ import com.otakumap.domain.auth.jwt.annotation.CurrentUser; import com.otakumap.domain.point.converter.PointConverter; +import com.otakumap.domain.point.dto.PointRequestDTO; import com.otakumap.domain.point.dto.PointResponseDTO; -import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.service.PointCommandService; import com.otakumap.domain.point.service.PointQueryservice; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -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.*; @RestController @RequestMapping("/api/points") @RequiredArgsConstructor @Validated public class PointController { - //private final PointCommandService pointCommandService; + private final PointCommandService pointCommandService; private final PointQueryservice pointQueryservice; @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") - @Parameters({ - @Parameter(name = "point", description = "충전할 포인트") - }) - public ApiResponse chargePoints(@RequestParam Long point, @CurrentUser User user) { - // 포인트 충전 서비스 호출 - //Point pointRecord = pointCommandService.chargePoints(user, point); - - // 성공적인 응답 반환 - //return ApiResponse.onSuccess("충전 성공하였습니다.", pointRecord); - return null; + public ApiResponse chargePoints( + @RequestBody @Valid PointRequestDTO.PointChargeDTO request, + @CurrentUser User user + ) { + pointCommandService.chargePoints(user, request.getPoint()); + return ApiResponse.onSuccess("충전 성공하였습니다."); } @Operation(summary = "포인트 충전 내역 확인", description = "포인트 충전 내역을 확인합니다. page는 1부터 시작합니다.") - @PostMapping("/transactions/charges") - public ApiResponse getChargePointList(@CurrentUser User user, @RequestParam(name = "page") Integer page) { - return ApiResponse.onSuccess(PointConverter.pointPreViewListDTO(pointQueryservice.getChargePointList(user, page))); + @PostMapping("/transactions/charges") + public ApiResponse getChargePointList(@CurrentUser User user, @RequestParam(name = "page") Integer page) { + return ApiResponse.onSuccess(PointConverter.pointPreViewListDTO(pointQueryservice.getChargePointList(user, page))); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java index 6e38ddb9..0f5fe221 100644 --- a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -1,9 +1,12 @@ package com.otakumap.domain.point.converter; +import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -28,4 +31,15 @@ public static PointResponseDTO.PointPreViewListDTO pointPreViewListDTO(Page Date: Wed, 19 Feb 2025 01:36:36 +0900 Subject: [PATCH 458/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aeddc806..2c0cb052 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -76,5 +76,6 @@ jobs: sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest + sudo docker-compose down sudo docker-compose up -d sudo docker image prune -f \ No newline at end of file From 9bb4dd35691aba3f6041570d344f7cf3e3c8c5b0 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 03:15:22 +0900 Subject: [PATCH 459/516] =?UTF-8?q?Refactor:=20=EB=AA=85=EC=86=8C=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20API=20=EB=B0=98=ED=99=98=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/PlaceShortReviewConverter.java | 2 +- .../dto/PlaceShortReviewResponseDTO.java | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java index fe6ab960..2234ae5f 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/place_short_review/converter/PlaceShortReviewConverter.java @@ -15,7 +15,7 @@ public class PlaceShortReviewConverter { public static PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO placeShortReviewUserDTO(User user) { return PlaceShortReviewResponseDTO.PlaceShortReviewUserDTO.builder() - .userId(user.getUserId()) + .userId(user.getId()) .nickname(user.getNickname()) .profileImage(user.getProfileImage().getFileUrl()) .build(); diff --git a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java index 158d080b..29bfe42a 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/place_short_review/dto/PlaceShortReviewResponseDTO.java @@ -15,11 +15,11 @@ public class PlaceShortReviewResponseDTO { @NoArgsConstructor @AllArgsConstructor public static class PlaceShortReviewListDTO { - Long placeId; - String placeName; - Integer currentPage; - Integer totalPages; - List shortReviews; + private Long placeId; + private String placeName; + private Integer currentPage; + private Integer totalPages; + private List shortReviews; } @Builder @@ -27,13 +27,13 @@ public static class PlaceShortReviewListDTO { @NoArgsConstructor @AllArgsConstructor public static class PlaceShortReviewDTO { - Long id; - PlaceShortReviewUserDTO user; - String content; - Float rating; - LocalDateTime createdAt; - Long likes; - Long dislikes; + private Long id; + private PlaceShortReviewUserDTO user; + private String content; + private Float rating; + private LocalDateTime createdAt; + private Long likes; + private Long dislikes; } @@ -42,9 +42,9 @@ public static class PlaceShortReviewDTO { @NoArgsConstructor @AllArgsConstructor public static class PlaceShortReviewUserDTO { - String userId; - String nickname; - String profileImage; + private Long userId; + private String nickname; + private String profileImage; } @Builder From 09f5341268e02d3850c82e60623e85bcff9fbf41 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 04:25:23 +0900 Subject: [PATCH 460/516] =?UTF-8?q?Feat:=20=EA=B2=B0=EC=A0=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20api=20..=20->=20=EA=B2=B0=EC=A0=9C=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EA=B0=80=20=EC=97=86=EB=8B=A4=EB=8A=94=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/controller/PaymentController.java | 10 +--- .../payment/dto/PaymentVerifyRequest.java | 8 ++- .../entity/{Payment.java => UserPayment.java} | 13 +++-- .../payment/repository/PaymentRepository.java | 7 ++- .../service/PaymentCommandServiceImpl.java | 53 +++++++++++++++---- .../point/controller/PointController.java | 2 +- .../point/converter/PointConverter.java | 19 +++++-- .../domain/point/dto/PointRequestDTO.java | 3 ++ .../otakumap/domain/point/entity/Point.java | 23 ++++---- .../point/repository/PointRepository.java | 1 - .../point/service/PointCommandService.java | 2 +- .../service/PointCommandServiceImpl.java | 22 ++++---- 12 files changed, 108 insertions(+), 55 deletions(-) rename src/main/java/com/otakumap/domain/payment/entity/{Payment.java => UserPayment.java} (78%) diff --git a/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java b/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java index dd354bef..2a74e295 100644 --- a/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java @@ -7,8 +7,6 @@ import com.otakumap.global.apiPayload.ApiResponse; import com.siot.IamportRestClient.exception.IamportResponseException; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,14 +26,10 @@ public class PaymentController { @Operation(summary = "결제 검증", description = "결제가 제대로 진행됐는지 검증합니다.") @PostMapping("/verify") - @Parameters({ - @Parameter(name = "imp_uid", description = "IAMPORT 결제 고유 ID"), - @Parameter(name = "merchant_uid", description = "주문 ID"), - @Parameter(name = "amount", description = "결제 금액") - }) public ApiResponse verifyPayment( @Valid @RequestBody PaymentVerifyRequest request, - @CurrentUser User user) throws IamportResponseException, IOException { + @CurrentUser User user + ) throws IamportResponseException, IOException { // 결제 검증 서비스 호출 paymentCommandService.verifyPayment(user, request); diff --git a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java index 8c2785a5..cbdbcac2 100644 --- a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java +++ b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java @@ -6,11 +6,9 @@ @Getter public class PaymentVerifyRequest { @NotNull(message = "imp_uid는 필수입니다.") - private String impUid; - + String impUid; @NotNull(message = "merchant_uid는 필수입니다.") - private String merchantUid; - + String merchantUid; @NotNull(message = "amount는 필수입니다.") - private Long amount; + Long amount; } diff --git a/src/main/java/com/otakumap/domain/payment/entity/Payment.java b/src/main/java/com/otakumap/domain/payment/entity/UserPayment.java similarity index 78% rename from src/main/java/com/otakumap/domain/payment/entity/Payment.java rename to src/main/java/com/otakumap/domain/payment/entity/UserPayment.java index adef9e15..cd4c1095 100644 --- a/src/main/java/com/otakumap/domain/payment/entity/Payment.java +++ b/src/main/java/com/otakumap/domain/payment/entity/UserPayment.java @@ -1,7 +1,7 @@ package com.otakumap.domain.payment.entity; - import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.user.entity.User; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; @@ -17,8 +17,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Table(name = "payment") -public class Payment extends BaseEntity{ +public class UserPayment extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -43,4 +42,12 @@ public class Payment extends BaseEntity{ @Enumerated(EnumType.STRING) @Column(nullable = false) private PaymentStatus status = PaymentStatus.PENDING; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "point_id", nullable = false) + private Point point; + + public void setPoint(Point point) { + this.point = point; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java b/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java index f3d49e38..5a398f75 100644 --- a/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java +++ b/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java @@ -1,10 +1,13 @@ package com.otakumap.domain.payment.repository; -import com.otakumap.domain.payment.entity.Payment; +import com.otakumap.domain.payment.entity.UserPayment; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository -public interface PaymentRepository extends JpaRepository { +public interface PaymentRepository extends JpaRepository { boolean existsByMerchantUid(String merchantUid); + Optional findByMerchantUid(String merchantUid); } diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java index cbcb3a3b..49f801f4 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -1,20 +1,25 @@ package com.otakumap.domain.payment.service; import com.otakumap.domain.payment.dto.PaymentVerifyRequest; +import com.otakumap.domain.payment.entity.UserPayment; +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.payment.repository.PaymentRepository; +import com.otakumap.domain.point.converter.PointConverter; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.point.repository.PointRepository; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.PaymentHandler; +import com.siot.IamportRestClient.IamportClient; import com.siot.IamportRestClient.exception.IamportResponseException; import com.siot.IamportRestClient.response.IamportResponse; import com.siot.IamportRestClient.response.Payment; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.siot.IamportRestClient.IamportClient; import java.io.IOException; +import java.time.LocalDateTime; @Service @RequiredArgsConstructor @@ -23,10 +28,12 @@ public class PaymentCommandServiceImpl implements PaymentCommandService { private final IamportClient iamportClient; private final PointRepository pointRepository; + private final PaymentRepository paymentRepository; + //private final PointConverter pointConverter; @Transactional public void verifyPayment(User user, PaymentVerifyRequest request) throws IOException, IamportResponseException { - // 1. 아임포트 결제 정보 조회 + // 아임포트 결제 정보 조회 IamportResponse paymentResponse = iamportClient.paymentByImpUid(request.getImpUid()); Payment payment = paymentResponse.getResponse(); @@ -34,23 +41,51 @@ public void verifyPayment(User user, PaymentVerifyRequest request) throws IOExce throw new PaymentHandler(ErrorStatus.PAYMENT_NOT_FOUND); } - // 2. 결제 상태 확인 + // 결제 상태 확인 if (!"paid".equals(payment.getStatus())) { throw new PaymentHandler(ErrorStatus.PAYMENT_STATUS_INVALID); } - // 3. 결제 금액 검증 + // 결제 금액 검증 if (!payment.getAmount().equals(request.getAmount())) { throw new PaymentHandler(ErrorStatus.PAYMENT_AMOUNT_MISMATCH); } - // 4. 중복 결제 방지 - if (pointRepository.findByMerchantUid(request.getMerchantUid()).isPresent()) { + // 중복 결제 방지 + if (paymentRepository.findByMerchantUid(request.getMerchantUid()).isPresent()) { throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); } - // 5. 포인트 저장 - Point point = new Point(user, request.getMerchantUid(), request.getAmount()); - pointRepository.save(point); + // 결제 정보 저장 + UserPayment userPayment = UserPayment.builder() + .user(user) + .impUid(payment.getImpUid()) + .merchantUid(payment.getMerchantUid()) + .amount(payment.getAmount().longValue()) + .verifiedAt(LocalDateTime.now()) + .status(PaymentStatus.PAID) + .build(); + + paymentRepository.save(userPayment); + + System.out.println("✅ Payment 정보: " + payment); + System.out.println("✅ UserPayment 정보: " + userPayment); + + // 포인트 저장 + Point point = new Point( + Long.valueOf(String.valueOf(payment.getAmount())), // 충전된 포인트 + LocalDateTime.now(), // 충전 시간 + PaymentStatus.PAID, // 상태 설정 + user, // 사용자 정보 + userPayment // 결제 정보 + ); + + System.out.println("✅ 생성된 Point 정보: " + point); + + pointRepository.save(point); // 포인트 저장 + + // 결제 정보와 포인트 정보를 연결 + userPayment.setPoint(point); + paymentRepository.save(userPayment); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 42e97eb1..a0ec48ba 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -28,7 +28,7 @@ public ApiResponse chargePoints( @RequestBody @Valid PointRequestDTO.PointChargeDTO request, @CurrentUser User user ) { - pointCommandService.chargePoints(user, request.getPoint()); + pointCommandService.chargePoints(user, request.getPoint(), request.getMerchantUid()); return ApiResponse.onSuccess("충전 성공하였습니다."); } diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java index 0f5fe221..6bc3cd1e 100644 --- a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -1,5 +1,6 @@ package com.otakumap.domain.point.converter; +import com.otakumap.domain.payment.entity.UserPayment; import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; @@ -18,6 +19,7 @@ public static PointResponseDTO.PointPreViewDTO pointPreViewDTO(Point point){ .chargedAt(point.getChargedAt()) .build(); } + public static PointResponseDTO.PointPreViewListDTO pointPreViewListDTO(Page pointList) { List pointPreViewDTOList = pointList.stream() .map(PointConverter::pointPreViewDTO).collect(Collectors.toList()); @@ -32,14 +34,21 @@ public static PointResponseDTO.PointPreViewListDTO pointPreViewListDTO(Page transactionList = new ArrayList<>(); - public Point(User user, String merchantUid, Long point) { - this.user = user; - this.merchantUid = merchantUid; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_payment_id", nullable = false) + private UserPayment userPayment; + + public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User user, UserPayment userPayment) { this.point = point; + this.chargedAt = chargedAt; + this.status = status; + this.user = user; + this.userPayment = userPayment; // userPayment 필드를 추가로 설정 } + } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java index 7a564b4c..3b407117 100644 --- a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java +++ b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java @@ -8,6 +8,5 @@ import java.util.Optional; public interface PointRepository extends JpaRepository { - Optional findByMerchantUid(String merchantUid); Page findAllByUser(User user, PageRequest pageRequest); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java index 0e3dce75..a2a6d339 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java @@ -3,5 +3,5 @@ import com.otakumap.domain.user.entity.User; public interface PointCommandService { - void chargePoints(User user, Long point); + void chargePoints(User user, Long point, String merchantUid); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java index 65a577b2..ddb277ef 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java @@ -1,16 +1,17 @@ package com.otakumap.domain.point.service; +import com.otakumap.domain.payment.entity.UserPayment; import com.otakumap.domain.payment.repository.PaymentRepository; import com.otakumap.domain.point.converter.PointConverter; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.point.repository.PointRepository; import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.PaymentHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.UUID; - @Service @RequiredArgsConstructor @Transactional @@ -20,15 +21,18 @@ public class PointCommandServiceImpl implements PointCommandService { private final PointRepository pointRepository; @Override - public void chargePoints(User user, Long point) { - // 랜덤 merchantUid 생성 (중복 방지) - String merchantUid; - do { - merchantUid = UUID.randomUUID().toString(); - } while (paymentRepository.existsByMerchantUid(merchantUid)); + public void chargePoints(User user, Long point, String merchantUid) { + + if (paymentRepository.existsByMerchantUid(merchantUid)) { + throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); + } + + // UserPayment 조회 + UserPayment userPayment = paymentRepository.findByMerchantUid(merchantUid) + .orElseThrow(() -> new PaymentHandler(ErrorStatus.PAYMENT_NOT_FOUND)); // 포인트 충전 내역 저장 - Point pointRecord = PointConverter.createPoint(user, point, merchantUid); + Point pointRecord = PointConverter.savePoint(user, point, userPayment); pointRepository.save(pointRecord); } } From 80a23a4d6b5905431de5c5c0522bbcf4ba91b752 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 19 Feb 2025 04:27:10 +0900 Subject: [PATCH 461/516] =?UTF-8?q?Feat:=20Error=20status=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/global/apiPayload/code/status/ErrorStatus.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 5e3bbcb8..87f46e0b 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -59,6 +59,9 @@ public enum ErrorStatus implements BaseErrorCode { EVENT_TYPE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4006", "존재하지 않는 이벤트 종류입니다."), EVENT_STATUS_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4007", "존재하지 않는 이벤트 상태입니다."), + // 이벤트 검색 관련 에러 + EVENT_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "EVENT4008", "검색된 이벤트가 없습니다."), + // 후기 검색 관련 에러 REVIEW_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH4001", "검색된 후기가 없습니다."), From bad0b0469043cabb477ed9924cd772753949676d Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 19 Feb 2025 04:28:00 +0900 Subject: [PATCH 462/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=EB=AA=85/=EC=9E=91=ED=92=88?= =?UTF-8?q?=EB=AA=85=EC=9C=BC=EB=A1=9C=20=EA=B2=80=EC=83=89=20repository?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SearchRepositoryCustom.java | 8 +++ .../SearchRepositoryCustomImpl.java | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java index 2a2d53c2..76ad2050 100644 --- a/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustom.java @@ -1,11 +1,19 @@ package com.otakumap.domain.search.repository; +import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.place.entity.Place; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import java.util.List; public interface SearchRepositoryCustom { + // 진행 전 or 진행 중인 이벤트에서 검색 List searchEventsByKeyword(String keyword); + + // 모든 이벤트에서 검색 + Page searchAllEventsByKeyword(String keyword, Pageable pageRequest); + List searchPlacesByKeyword(String keyword); } diff --git a/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java index bb60f601..de01f17a 100644 --- a/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java +++ b/src/main/java/com/otakumap/domain/search/repository/SearchRepositoryCustomImpl.java @@ -2,6 +2,8 @@ import com.otakumap.domain.animation.entity.QAnimation; +import com.otakumap.domain.event.converter.EventConverter; +import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event.entity.enums.EventStatus; @@ -9,9 +11,16 @@ import com.otakumap.domain.mapping.QPlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.entity.QPlace; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.SearchHandler; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Expression; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; import java.util.List; @@ -23,6 +32,7 @@ public class SearchRepositoryCustomImpl implements SearchRepositoryCustom { private final JPAQueryFactory queryFactory; @Override + // 진행 전 or 진행 중인 이벤트 중에서 검색 public List searchEventsByKeyword(String keyword) { QEvent qEvent = QEvent.event; @@ -41,6 +51,40 @@ public List searchEventsByKeyword(String keyword) { .fetch(); } + @Override + // 모든 이벤트에서 검색 + public Page searchAllEventsByKeyword(String keyword, Pageable pageRequest) { + + QEvent qEvent = QEvent.event; + QEventAnimation qEventAnimation = QEventAnimation.eventAnimation; + QAnimation qAnimation = QAnimation.animation; + + BooleanBuilder condition = new BooleanBuilder(); + condition.or(qEvent.title.containsIgnoreCase(keyword)) + .or(qAnimation.name.containsIgnoreCase(keyword)); + + // 전체 건수 조회 + Long total = createBaseQuery(qEvent.id.count(), qEvent, qEventAnimation, qAnimation, condition) + .fetchOne(); + if(total == null) { + throw new SearchHandler(ErrorStatus.EVENT_SEARCH_NOT_FOUND); + } + + List events = queryFactory.selectFrom(qEvent) + .leftJoin(qEventAnimation).on(qEventAnimation.event.eq(qEvent)) + .leftJoin(qAnimation).on(qEventAnimation.animation.eq(qAnimation)) + .where(condition) + .offset(pageRequest.getOffset()) + .limit(pageRequest.getPageSize()) + .fetch(); + + List content = events.stream() + .map(EventConverter::toEventDTO) + .toList(); + + return new PageImpl<>(content, pageRequest, total); + } + @Override public List searchPlacesByKeyword(String keyword) { @@ -58,4 +102,14 @@ public List searchPlacesByKeyword(String keyword) { .where(condition) .fetch(); } + + private JPAQuery createBaseQuery(Expression selectClause, QEvent qEvent, + QEventAnimation qEventAnimation, + QAnimation qAnimation, BooleanBuilder condition) { + return queryFactory.select(selectClause) + .from(qEvent) + .leftJoin(qEventAnimation).on(qEventAnimation.event.eq(qEvent)) + .leftJoin(qAnimation).on(qEventAnimation.animation.eq(qAnimation)) + .where(condition); + } } From 088c8a3ab418685fc77b5e64cbbec73c4bd11ebb Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 19 Feb 2025 04:28:37 +0900 Subject: [PATCH 463/516] =?UTF-8?q?Feat:=20Service=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/service/EventQueryService.java | 1 + .../domain/event/service/EventQueryServiceImpl.java | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event/service/EventQueryService.java b/src/main/java/com/otakumap/domain/event/service/EventQueryService.java index f38c69a2..15b48378 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventQueryService.java +++ b/src/main/java/com/otakumap/domain/event/service/EventQueryService.java @@ -4,4 +4,5 @@ public interface EventQueryService { EventResponseDTO.EventDetailDTO getEventDetail(Long eventId); + EventResponseDTO.EventSearchResultDTO searchEventByKeyword(String keyword, Integer page, Integer size); } diff --git a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java index 705498b7..e5a5eea5 100644 --- a/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event/service/EventQueryServiceImpl.java @@ -4,9 +4,12 @@ import com.otakumap.domain.event.dto.EventResponseDTO; import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.search.repository.SearchRepositoryCustom; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,6 +21,7 @@ public class EventQueryServiceImpl implements EventQueryService{ private final EventRepository eventRepository; + private final SearchRepositoryCustom searchRepository; @Override public EventResponseDTO.EventDetailDTO getEventDetail(Long eventId) { @@ -28,4 +32,12 @@ public EventResponseDTO.EventDetailDTO getEventDetail(Long eventId) { return EventConverter.toEventDetailDTO(event, animationName); } + + @Override + public EventResponseDTO.EventSearchResultDTO searchEventByKeyword(String keyword, Integer page, Integer size) { + + Page pagedEventDTOs = searchRepository.searchAllEventsByKeyword(keyword, PageRequest.of(page, size)); + + return EventConverter.toEventSearchResultDTO(pagedEventDTOs); + } } From 5d3b07509916f97cb131714edcf87cc31bfda9ae Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Wed, 19 Feb 2025 04:29:10 +0900 Subject: [PATCH 464/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8/?= =?UTF-8?q?=EC=9E=91=ED=92=88=EB=AA=85=20=EA=B2=80=EC=83=89=20controller?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event/controller/EventController.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event/controller/EventController.java b/src/main/java/com/otakumap/domain/event/controller/EventController.java index 8d5541ae..0ecf7c33 100644 --- a/src/main/java/com/otakumap/domain/event/controller/EventController.java +++ b/src/main/java/com/otakumap/domain/event/controller/EventController.java @@ -54,4 +54,14 @@ public ApiResponse searchEventByCategory( @RequestParam Integer size) { return ApiResponse.onSuccess(EventConverter.toEventSearchResultDTO(eventCustomService.searchEventByCategory(genre, status, type, page, size))); } + + @Operation(summary = "이벤트 이름/작품명으로 이벤트 검색", description = "이벤트 이름과 작품명으로 이벤트를 검색합니다.") + @GetMapping("/events/search") + public ApiResponse searchEventByKeyword( + @Parameter(description = "검색어") @RequestParam String keyword, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam Integer page, + @Parameter(description = "한 페이지에 가져올 이벤트 개수", example = "10") @RequestParam Integer size) { + + return ApiResponse.onSuccess(eventQueryService.searchEventByKeyword(keyword, page, size)); + } } From a9d873f73c3ad58cd50f7aeceaafde61fdf486f3 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 05:59:21 +0900 Subject: [PATCH 465/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 3 + .../repository/EventReviewRepository.java | 3 + .../place_review/entity/PlaceReview.java | 4 + .../repository/PlaceReviewRepository.java | 6 +- .../otakumap/domain/point/entity/Point.java | 13 ++++ .../reviews/controller/ReviewController.java | 12 +++ .../domain/reviews/dto/ReviewResponseDTO.java | 22 ++++-- .../repository/ReviewRepositoryCustom.java | 3 + .../repository/ReviewRepositoryImpl.java | 78 +++++++++++++++++++ .../reviews/service/ReviewCommandService.java | 2 + .../service/ReviewCommandServiceImpl.java | 7 ++ .../transaction/entity/Transaction.java | 12 +++ .../repository/TransactionRepository.java | 4 + .../apiPayload/code/status/ErrorStatus.java | 8 +- .../exception/handler/TransactionHandler.java | 10 +++ 15 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/otakumap/global/apiPayload/exception/handler/TransactionHandler.java diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index b5b80244..b0377624 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -37,6 +37,9 @@ public class EventReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; + @Column(columnDefinition = "bigint default 0 not null") + private Long price; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") private List images = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 3c9f870f..008f1759 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -16,4 +16,7 @@ public interface EventReviewRepository extends JpaRepository Optional findByRouteId(Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.route.id = :routeId") Optional findUserByRouteId(@Param("routeId") Long routeId); + @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") + User findUserById(@Param("reviewId") Long reviewId); } + diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 40b9b5ae..78ae6f8a 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -36,6 +36,10 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long view; + @Column(columnDefinition = "bigint default 0 not null") + private Long price; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview", orphanRemoval = true) private List images = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index d93a6351..25155da5 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -1,7 +1,6 @@ package com.otakumap.domain.place_review.repository; import com.otakumap.domain.place_review.entity.PlaceReview; -import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -11,8 +10,6 @@ import java.util.Optional; -import java.util.Optional; - public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { Page findAllByUserId(Long userId, PageRequest pageRequest); @@ -20,5 +17,6 @@ public interface PlaceReviewRepository extends JpaRepository, Optional findByRouteId(Long routeId); @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.route.id = :routeId") Optional findUserByRouteId(@Param("routeId") Long routeId); - PlaceReview findAllByRoute(Route route); + @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.id = :reviewId") + User findUserById(@Param("reviewId") Long reviewId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index a419dd88..78fd445b 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -65,4 +65,17 @@ public Point(User user, String merchantUid, Long point) { this.merchantUid = merchantUid; this.point = point; } + + public void addPoint(Long point) { + this.point += point; + } + + public Long subPoint(Long point) { + this.point -= point; + return this.point; + } + + public boolean isAffordable(Long point) { + return this.point >= point; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index c327d40b..308523d9 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -82,4 +82,16 @@ public ApiResponse createReview(@Parameter(c ReviewResponseDTO.CreatedReviewDTO createdReview = reviewCommandService.createReview(request, user, images); return ApiResponse.onSuccess(createdReview); } + + @PostMapping(value = "/reviews/purchase") + @Operation(summary = "후기 구매", description = "후기를 구매합니다.") + @Parameters({ + @Parameter(name = "reviewId", description = "이벤트 or 명소의 후기 id 입니다."), + @Parameter(name = "type", description = "리뷰의 종류를 특정합니다. 'EVENT' 또는 'PLACE' 여야 합니다.") + }) + public ApiResponse purchaseReview(@CurrentUser User user, + @RequestParam @ValidReviewId Long reviewId, + @RequestParam(defaultValue = "PLACE") ReviewType type) { + return ApiResponse.onSuccess(reviewCommandService.purchaseReview(user, reviewId, type)); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index bb5372ef..23b82978 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -17,7 +17,7 @@ public class ReviewResponseDTO { @NoArgsConstructor @AllArgsConstructor public static class Top7ReviewPreViewListDTO { - List reviews; + private List reviews; } @Builder @@ -25,12 +25,12 @@ public static class Top7ReviewPreViewListDTO { @NoArgsConstructor @AllArgsConstructor public static class Top7ReviewPreViewDTO { - Long id; - String title; - ImageResponseDTO.ImageDTO reviewImage; - Long view; - String type; - LocalDateTime createdAt; + private Long id; + private String title; + private ImageResponseDTO.ImageDTO reviewImage; + private Long view; + private String type; + private LocalDateTime createdAt; } @Builder @@ -75,4 +75,12 @@ public static class CreatedReviewDTO { String title; LocalDateTime createdAt; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PurchaseReviewDTO { + private Long remainingPoints; + } } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java index ac10ac18..936c3a8a 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java @@ -1,9 +1,12 @@ package com.otakumap.domain.reviews.repository; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; public interface ReviewRepositoryCustom { Page getReviewsByKeyword(String keyword, int page, int size, String sort); ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews(); + ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type); } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index b9fcc810..5fd1f229 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -4,16 +4,27 @@ import com.otakumap.domain.event.entity.QEvent; import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.entity.QEventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.mapping.QEventAnimation; import com.otakumap.domain.mapping.QPlaceAnimation; import com.otakumap.domain.mapping.QPlaceReviewPlace; import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.repository.PointRepository; import com.otakumap.domain.reviews.converter.ReviewConverter; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.transaction.entity.Transaction; +import com.otakumap.domain.transaction.enums.TransactionType; +import com.otakumap.domain.transaction.repository.TransactionRepository; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.apiPayload.exception.handler.SearchHandler; +import com.otakumap.global.apiPayload.exception.handler.TransactionHandler; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.dsl.StringPath; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -26,6 +37,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,6 +46,10 @@ public class ReviewRepositoryImpl implements ReviewRepositoryCustom { private final JPAQueryFactory queryFactory; + private final EventReviewRepository eventReviewRepository; + private final PointRepository pointRepository; + private final PlaceReviewRepository placeReviewRepository; + private final TransactionRepository transactionRepository; @Override public Page getReviewsByKeyword(String keyword, int page, int size, String sort) { @@ -144,4 +160,66 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { return ReviewConverter.top7ReviewPreViewListDTO(top7Reviews); } + + @Override + public ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type) { + Point buyerPoint = pointRepository.findTopByUserOrderByCreatedAtDesc(user); + // 이벤트 리뷰인 경우 + if(type == ReviewType.EVENT) { + EventReview review = eventReviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + if (transactionRepository.existsByPoint_UserAndEventReview(user, review)) { + throw new TransactionHandler(ErrorStatus.PURCHASE_ALREADY_EXISTS); + } + + return processReviewPurchase(user, buyerPoint, review, review.getPrice(), + eventReviewRepository.findUserById(reviewId), true); + } + // 장소 리뷰인 경우 + PlaceReview review = placeReviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + if (transactionRepository.existsByPoint_UserAndPlaceReview(user, review)) { + throw new TransactionHandler(ErrorStatus.PURCHASE_ALREADY_EXISTS); + } + + return processReviewPurchase(user, buyerPoint, review, review.getPrice(), + placeReviewRepository.findUserById(reviewId), false); + } + + private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( + User user, Point buyerPoint, Object review, Long price, User seller, boolean isEventReview) { + // 무료 글인 경우 + if(price == 0L) { + throw new TransactionHandler(ErrorStatus.PURCHASE_FREE_CONTENT); + } + // 포인트 부족 확인 + if (!buyerPoint.isAffordable(price)) { + throw new TransactionHandler(ErrorStatus.PURCHASE_INSUFFICIENT_POINTS); + } + // 글쓴이와 구매자가 다른지 확인 + if(Objects.equals(user.getId(), seller.getId())) { + throw new TransactionHandler(ErrorStatus.PURCHASE_SELF_CONTENT); + } + + Point sellerPoint = pointRepository.findTopByUserOrderByCreatedAtDesc(seller); + + int priceInt = Math.toIntExact(price); + // 거래 내역 저장(사용한 것과 번 것) + if (isEventReview) { + transactionRepository.save(new Transaction(buyerPoint, TransactionType.USAGE, priceInt, (EventReview) review, null)); + transactionRepository.save(new Transaction(sellerPoint, TransactionType.EARNING, priceInt, (EventReview) review, null)); + } else { + transactionRepository.save(new Transaction(buyerPoint, TransactionType.USAGE, priceInt, null, (PlaceReview) review)); + transactionRepository.save(new Transaction(sellerPoint, TransactionType.EARNING, priceInt, null, (PlaceReview) review)); + } + + // 포인트 수정 후 업데이트 + sellerPoint.addPoint(price); + Long remainingPoints = buyerPoint.subPoint(price); + pointRepository.save(sellerPoint); + pointRepository.save(buyerPoint); + return ReviewResponseDTO.PurchaseReviewDTO.builder() + .remainingPoints(remainingPoints) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java index 5233d9d9..17a03f92 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandService.java @@ -2,9 +2,11 @@ import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.user.entity.User; import org.springframework.web.multipart.MultipartFile; public interface ReviewCommandService { ReviewResponseDTO.CreatedReviewDTO createReview(ReviewRequestDTO.CreateDTO request, User user, MultipartFile[] images); + ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 9cbd37e2..09d07990 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -18,6 +18,7 @@ import com.otakumap.domain.reviews.dto.ReviewRequestDTO; import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; @@ -40,6 +41,7 @@ @Service @RequiredArgsConstructor public class ReviewCommandServiceImpl implements ReviewCommandService { + private final ReviewRepositoryCustom reviewRepositoryCustom; private final PlaceReviewRepository placeReviewRepository; private final EventReviewRepository eventReviewRepository; private final AnimationRepository animationRepository; @@ -136,4 +138,9 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } + + @Override + public ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type) { + return reviewRepositoryCustom.purchaseReview(user, reviewId, type); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java index 81356cee..88dad15e 100644 --- a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java +++ b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java @@ -41,4 +41,16 @@ public class Transaction extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_review_id", referencedColumnName = "id") private PlaceReview placeReview; + + public Transaction(Point point, TransactionType type, int amount, EventReview eventReview, PlaceReview placeReview) { + this.point = point; + this.type = type; + this.amount = amount; + + if(eventReview != null) { + this.eventReview = eventReview; + } else { + this.placeReview = placeReview; + } + } } diff --git a/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java b/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java index cd806523..c085e888 100644 --- a/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java +++ b/src/main/java/com/otakumap/domain/transaction/repository/TransactionRepository.java @@ -1,5 +1,7 @@ package com.otakumap.domain.transaction.repository; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.transaction.enums.TransactionType; import com.otakumap.domain.user.entity.User; @@ -10,4 +12,6 @@ public interface TransactionRepository extends JpaRepository{ List findAllByPoint_UserAndType(User user, TransactionType type, Pageable pageRequest); + Boolean existsByPoint_UserAndEventReview(User user, EventReview review); + Boolean existsByPoint_UserAndPlaceReview(User user, PlaceReview review); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index 5e3bbcb8..f7d238ef 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -112,7 +112,13 @@ public enum ErrorStatus implements BaseErrorCode { PAYMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "PAYMENT4001", "결제 정보를 찾을 수 없습니다."), PAYMENT_STATUS_INVALID(HttpStatus.BAD_REQUEST, "PAYMENT4002", "결제 상태가 유효하지 않습니다."), PAYMENT_AMOUNT_MISMATCH(HttpStatus.BAD_REQUEST, "PAYMENT4003", "결제 금액이 일치하지 않습니다."), - PAYMENT_DUPLICATE(HttpStatus.BAD_REQUEST, "PAYMENT4004", "이미 처리된 결제입니다."); + PAYMENT_DUPLICATE(HttpStatus.BAD_REQUEST, "PAYMENT4004", "이미 처리된 결제입니다."), + + // 내부 결제 관련 에러 + PURCHASE_FREE_CONTENT(HttpStatus.BAD_REQUEST, "PURCHASE4001", "무료이므로 결제가 불가능합니다."), + PURCHASE_INSUFFICIENT_POINTS(HttpStatus.FORBIDDEN, "PURCHASE4002", "포인트가 모자라 결제가 불가능합니다."), + PURCHASE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "PURCHASE4003", "이미 결제된 글입니다."), + PURCHASE_SELF_CONTENT(HttpStatus.BAD_REQUEST, "PURCHASE4004", "본인의 글은 결제할 수 없습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/otakumap/global/apiPayload/exception/handler/TransactionHandler.java b/src/main/java/com/otakumap/global/apiPayload/exception/handler/TransactionHandler.java new file mode 100644 index 00000000..bfcea6e6 --- /dev/null +++ b/src/main/java/com/otakumap/global/apiPayload/exception/handler/TransactionHandler.java @@ -0,0 +1,10 @@ +package com.otakumap.global.apiPayload.exception.handler; + +import com.otakumap.global.apiPayload.code.BaseErrorCode; +import com.otakumap.global.apiPayload.exception.GeneralException; + +public class TransactionHandler extends GeneralException { + public TransactionHandler(BaseErrorCode code) { + super(code); + } +} From 87f05e407a359eda0627810114d7439294d2bb4d Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 06:11:36 +0900 Subject: [PATCH 466/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=20price=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=B0=98=ED=99=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place_review/entity/PlaceReview.java | 1 - .../com/otakumap/domain/reviews/converter/ReviewConverter.java | 2 +- .../java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 78ae6f8a..f904783c 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -39,7 +39,6 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long price; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview", orphanRemoval = true) private List images = new ArrayList<>(); diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 1bdcc9b9..f6c3c40d 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -5,7 +5,6 @@ import com.otakumap.domain.image.converter.ImageConverter; import com.otakumap.domain.image.dto.ImageResponseDTO; import com.otakumap.domain.image.entity.Image; -import com.otakumap.domain.mapping.EventAnimation; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.mapping.PlaceReviewPlace; @@ -114,6 +113,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .title(eventReview.getTitle()) .view(eventReview.getView()) .content(eventReview.getContent()) + .price(eventReview.getPrice()) .reviewImages(eventReview.getImages().stream() .filter(Objects::nonNull) .map(ImageConverter::toImageDTO) diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 23b82978..7ca0fb23 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -57,6 +57,7 @@ public static class ReviewDetailDTO { String title; Long view; String content; + Long price; List reviewImages; String userName; From 7f2d2f95463260a65a4b6475a1b4c136811ea2e3 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 11:49:05 +0900 Subject: [PATCH 467/516] =?UTF-8?q?Refactor:=20=ED=8C=90=EB=A7=A4=EC=9E=90?= =?UTF-8?q?=20point=20=EA=B0=9D=EC=B2=B4=EA=B0=80=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/point/entity/Point.java | 9 ++++++++- .../repository/ReviewRepositoryImpl.java | 17 +++++++++++------ .../com/otakumap/domain/user/entity/User.java | 5 ++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 78fd445b..1ac79c0f 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -46,7 +46,7 @@ public class Point extends BaseEntity { private String chargedBy; // 주문 ID - @Column(name = "merchant_uid", unique = true, nullable = false) + @Column(name = "merchant_uid") private String merchantUid; // Iamport 결제 고유 ID @@ -60,6 +60,13 @@ public class Point extends BaseEntity { @OneToMany(mappedBy = "point", cascade = CascadeType.ALL) private List transactionList = new ArrayList<>(); + public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User user) { + this.point = point; + this.chargedAt = chargedAt; + this.status = status; + this.user = user; + } + public Point(User user, String merchantUid, Long point) { this.user = user; this.merchantUid = merchantUid; diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 5fd1f229..267756d5 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -8,6 +8,7 @@ import com.otakumap.domain.mapping.QEventAnimation; import com.otakumap.domain.mapping.QPlaceAnimation; import com.otakumap.domain.mapping.QPlaceReviewPlace; +import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.place.entity.QPlace; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.entity.QPlaceReview; @@ -34,6 +35,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -202,6 +204,15 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( } Point sellerPoint = pointRepository.findTopByUserOrderByCreatedAtDesc(seller); + if(sellerPoint == null) { + sellerPoint = new Point(0L, LocalDateTime.now(), PaymentStatus.PAID, seller); + } + + // 포인트 수정 후 업데이트 + sellerPoint.addPoint(price); + Long remainingPoints = buyerPoint.subPoint(price); + pointRepository.save(sellerPoint); + pointRepository.save(buyerPoint); int priceInt = Math.toIntExact(price); // 거래 내역 저장(사용한 것과 번 것) @@ -212,12 +223,6 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( transactionRepository.save(new Transaction(buyerPoint, TransactionType.USAGE, priceInt, null, (PlaceReview) review)); transactionRepository.save(new Transaction(sellerPoint, TransactionType.EARNING, priceInt, null, (PlaceReview) review)); } - - // 포인트 수정 후 업데이트 - sellerPoint.addPoint(price); - Long remainingPoints = buyerPoint.subPoint(price); - pointRepository.save(sellerPoint); - pointRepository.save(buyerPoint); return ReviewResponseDTO.PurchaseReviewDTO.builder() .remainingPoints(remainingPoints) .build(); diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 5c0a53d0..882c32f7 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.place_like.entity.PlaceLike; -import com.otakumap.domain.route.entity.Route; +import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.domain.user.entity.enums.Role; import com.otakumap.domain.user.entity.enums.SocialType; @@ -80,6 +80,9 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List routeLikes = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List points = new ArrayList<>(); + public void encodePassword(String password) { this.password = password; } From d5d4c78569d33633ffdc6757b03efd0c61f11035 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 14:11:23 +0900 Subject: [PATCH 468/516] =?UTF-8?q?Refactor:=20=ED=9B=84=EA=B8=B0=20?= =?UTF-8?q?=EA=B5=AC=EB=A7=A4=20API=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/point/entity/Point.java | 2 +- .../domain/reviews/repository/ReviewRepositoryImpl.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 1ac79c0f..06ed1861 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -46,7 +46,7 @@ public class Point extends BaseEntity { private String chargedBy; // 주문 ID - @Column(name = "merchant_uid") + @Column(name = "merchant_uid", unique = true, nullable = false) private String merchantUid; // Iamport 결제 고유 ID diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 267756d5..f587af45 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -169,7 +169,7 @@ public ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long review // 이벤트 리뷰인 경우 if(type == ReviewType.EVENT) { EventReview review = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + .orElseThrow(() -> new ReviewHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); if (transactionRepository.existsByPoint_UserAndEventReview(user, review)) { throw new TransactionHandler(ErrorStatus.PURCHASE_ALREADY_EXISTS); } @@ -179,7 +179,7 @@ public ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long review } // 장소 리뷰인 경우 PlaceReview review = placeReviewRepository.findById(reviewId) - .orElseThrow(() -> new ReviewHandler(ErrorStatus.REVIEW_NOT_FOUND)); + .orElseThrow(() -> new ReviewHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); if (transactionRepository.existsByPoint_UserAndPlaceReview(user, review)) { throw new TransactionHandler(ErrorStatus.PURCHASE_ALREADY_EXISTS); } @@ -199,12 +199,12 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( throw new TransactionHandler(ErrorStatus.PURCHASE_INSUFFICIENT_POINTS); } // 글쓴이와 구매자가 다른지 확인 - if(Objects.equals(user.getId(), seller.getId())) { + if (Objects.equals(user.getId(), seller.getId())) { throw new TransactionHandler(ErrorStatus.PURCHASE_SELF_CONTENT); } Point sellerPoint = pointRepository.findTopByUserOrderByCreatedAtDesc(seller); - if(sellerPoint == null) { + if (sellerPoint == null) { sellerPoint = new Point(0L, LocalDateTime.now(), PaymentStatus.PAID, seller); } From a23543f6c20224ecaa233a5392ea1049b228830c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 15:42:13 +0900 Subject: [PATCH 469/516] =?UTF-8?q?Fix:=20=ED=95=9C=EC=A4=84=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20entity=EC=97=90=20@OnDelete=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/event_reaction/entity/EventReaction.java | 3 +++ .../com/otakumap/domain/user_reaction/entity/UserReaction.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java b/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java index 63e6f864..9619c654 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java +++ b/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java @@ -5,6 +5,8 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; @Entity @Getter @@ -23,6 +25,7 @@ public class EventReaction extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_short_review_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) private EventShortReview eventShortReview; @Column(nullable = false) diff --git a/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java b/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java index b2bf1107..6e5a46d8 100644 --- a/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java +++ b/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java @@ -5,6 +5,8 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; @Entity @Getter @@ -23,6 +25,7 @@ public class UserReaction extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_short_review_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) private PlaceShortReview placeShortReview; @Column(nullable = false) From 950584ca6b91be7d6889c72a1217cb4508f30a30 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 15:49:15 +0900 Subject: [PATCH 470/516] =?UTF-8?q?Fix:=20event=5Freaction=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=EC=97=90=20review=20=EC=98=AC=EB=B0=94?= =?UTF-8?q?=EB=A5=B4=EA=B2=8C=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_reaction/converter/EventReactionConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java index 68f76905..c3a71f40 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java +++ b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java @@ -19,6 +19,7 @@ public static EventReactionResponseDTO.ReactionResponseDTO toReactionResponseDTO public static EventReaction toLike(User user, EventShortReview eventShortReview, boolean isLiked) { return EventReaction.builder() .user(user) + .eventShortReview(eventShortReview) .isLiked(isLiked) .build(); } From 7b93e35e15e79e47cb6e33bbe7d9229933fd0182 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 16:15:03 +0900 Subject: [PATCH 471/516] =?UTF-8?q?Refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=98=EC=9D=91=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EventReactionController.java | 28 ------------ .../converter/EventReactionConverter.java | 34 -------------- .../repository/EventReactionRepository.java | 10 ----- .../service/EventReactionCommandService.java | 8 ---- .../EventShortReviewReactionController.java | 28 ++++++++++++ .../EventShortReviewReactionConverter.java | 34 ++++++++++++++ .../EventShortReviewReactionRequestDTO.java} | 4 +- .../EventShortReviewReactionResponseDTO.java} | 4 +- .../entity/EventShortReviewReaction.java} | 6 +-- .../EventShortReviewReactionRepository.java | 10 +++++ ...ventShortReviewReactionCommandService.java | 8 ++++ ...hortReviewReactionCommandServiceImpl.java} | 44 +++++++++---------- 12 files changed, 109 insertions(+), 109 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java delete mode 100644 src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java delete mode 100644 src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java delete mode 100644 src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java create mode 100644 src/main/java/com/otakumap/domain/event_short_review_reaction/controller/EventShortReviewReactionController.java create mode 100644 src/main/java/com/otakumap/domain/event_short_review_reaction/converter/EventShortReviewReactionConverter.java rename src/main/java/com/otakumap/domain/{event_reaction/dto/EventReactionRequestDTO.java => event_short_review_reaction/dto/EventShortReviewReactionRequestDTO.java} (68%) rename src/main/java/com/otakumap/domain/{event_reaction/dto/EventReactionResponseDTO.java => event_short_review_reaction/dto/EventShortReviewReactionResponseDTO.java} (78%) rename src/main/java/com/otakumap/domain/{event_reaction/entity/EventReaction.java => event_short_review_reaction/entity/EventShortReviewReaction.java} (80%) create mode 100644 src/main/java/com/otakumap/domain/event_short_review_reaction/repository/EventShortReviewReactionRepository.java create mode 100644 src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandService.java rename src/main/java/com/otakumap/domain/{event_reaction/service/EventReactionCommandServiceImpl.java => event_short_review_reaction/service/EventShortReviewReactionCommandServiceImpl.java} (50%) diff --git a/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java b/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java deleted file mode 100644 index 82c8dfc5..00000000 --- a/src/main/java/com/otakumap/domain/event_reaction/controller/EventReactionController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.otakumap.domain.event_reaction.controller; - -import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.event_reaction.dto.EventReactionResponseDTO; -import com.otakumap.domain.event_reaction.converter.EventReactionConverter; -import com.otakumap.domain.event_reaction.entity.EventReaction; -import com.otakumap.domain.event_reaction.service.EventReactionCommandService; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/events/short-reviews/{reviewId}/reaction") -public class EventReactionController { - private final EventReactionCommandService eventReactionCommandService; - - @PostMapping - @Operation(summary = "이벤트 한줄 리뷰에 좋아요/싫어요 남기기 및 취소하기", description = "0을 요청하면 dislike, 1을 요청하면 like이며, 이미 존재하는 반응을 요청하면 취소됩니다.") - public ApiResponse reactToReview( - @CurrentUser User user, @PathVariable Long reviewId, @Valid @RequestBody int reactionType) { - EventReaction eventReaction = eventReactionCommandService.reactToReview(user, reviewId, reactionType); - return ApiResponse.onSuccess(EventReactionConverter.toReactionResponseDTO(eventReaction.getEventShortReview(), eventReaction)); - } -} diff --git a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java b/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java deleted file mode 100644 index c3a71f40..00000000 --- a/src/main/java/com/otakumap/domain/event_reaction/converter/EventReactionConverter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.otakumap.domain.event_reaction.converter; - -import com.otakumap.domain.event_reaction.dto.EventReactionResponseDTO; -import com.otakumap.domain.event_reaction.entity.EventReaction; -import com.otakumap.domain.event_short_review.entity.EventShortReview; -import com.otakumap.domain.user.entity.User; - -public class EventReactionConverter { - public static EventReactionResponseDTO.ReactionResponseDTO toReactionResponseDTO(EventShortReview eventShortReview, EventReaction eventReaction) { - return EventReactionResponseDTO.ReactionResponseDTO.builder() - .reviewId(eventShortReview.getId()) - .likes(eventShortReview.getLikes()) - .dislikes(eventShortReview.getDislikes()) - .isLiked(eventReaction.isLiked()) - .isDisliked(eventReaction.isDisliked()) - .build(); - } - - public static EventReaction toLike(User user, EventShortReview eventShortReview, boolean isLiked) { - return EventReaction.builder() - .user(user) - .eventShortReview(eventShortReview) - .isLiked(isLiked) - .build(); - } - - public static EventReaction toDislike(User user, EventShortReview eventShortReview, boolean isDisliked) { - return EventReaction.builder() - .user(user) - .eventShortReview(eventShortReview) - .isDisliked(isDisliked) - .build(); - } -} diff --git a/src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java b/src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java deleted file mode 100644 index 0de9baee..00000000 --- a/src/main/java/com/otakumap/domain/event_reaction/repository/EventReactionRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.otakumap.domain.event_reaction.repository; - -import com.otakumap.domain.event_reaction.entity.EventReaction; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface EventReactionRepository extends JpaRepository { - Optional findByUserIdAndEventShortReviewId(Long userId, Long eventShortReviewId); -} diff --git a/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java b/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java deleted file mode 100644 index 517a5b94..00000000 --- a/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandService.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.otakumap.domain.event_reaction.service; - -import com.otakumap.domain.event_reaction.entity.EventReaction; -import com.otakumap.domain.user.entity.User; - -public interface EventReactionCommandService { - EventReaction reactToReview(User user, Long reviewId, int reactionType); -} diff --git a/src/main/java/com/otakumap/domain/event_short_review_reaction/controller/EventShortReviewReactionController.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/controller/EventShortReviewReactionController.java new file mode 100644 index 00000000..efa274cf --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/controller/EventShortReviewReactionController.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.event_short_review_reaction.controller; + +import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.event_short_review_reaction.dto.EventShortReviewReactionResponseDTO; +import com.otakumap.domain.event_short_review_reaction.converter.EventShortReviewReactionConverter; +import com.otakumap.domain.event_short_review_reaction.entity.EventShortReviewReaction; +import com.otakumap.domain.event_short_review_reaction.service.EventShortReviewReactionCommandService; +import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/events/short-reviews/{reviewId}/reaction") +public class EventShortReviewReactionController { + private final EventShortReviewReactionCommandService eventReactionCommandService; + + @PostMapping + @Operation(summary = "이벤트 한줄 리뷰에 좋아요/싫어요 남기기 및 취소하기", description = "0을 요청하면 dislike, 1을 요청하면 like이며, 이미 존재하는 반응을 요청하면 취소됩니다.") + public ApiResponse reactToReview( + @CurrentUser User user, @PathVariable Long reviewId, @Valid @RequestBody int reactionType) { + EventShortReviewReaction eventShortReviewReaction = eventReactionCommandService.reactToReview(user, reviewId, reactionType); + return ApiResponse.onSuccess(EventShortReviewReactionConverter.toReactionResponseDTO(eventShortReviewReaction.getEventShortReview(), eventShortReviewReaction)); + } +} diff --git a/src/main/java/com/otakumap/domain/event_short_review_reaction/converter/EventShortReviewReactionConverter.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/converter/EventShortReviewReactionConverter.java new file mode 100644 index 00000000..25938c24 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/converter/EventShortReviewReactionConverter.java @@ -0,0 +1,34 @@ +package com.otakumap.domain.event_short_review_reaction.converter; + +import com.otakumap.domain.event_short_review_reaction.dto.EventShortReviewReactionResponseDTO; +import com.otakumap.domain.event_short_review_reaction.entity.EventShortReviewReaction; +import com.otakumap.domain.event_short_review.entity.EventShortReview; +import com.otakumap.domain.user.entity.User; + +public class EventShortReviewReactionConverter { + public static EventShortReviewReactionResponseDTO.ReactionResponseDTO toReactionResponseDTO(EventShortReview eventShortReview, EventShortReviewReaction eventShortReviewReaction) { + return EventShortReviewReactionResponseDTO.ReactionResponseDTO.builder() + .reviewId(eventShortReview.getId()) + .likes(eventShortReview.getLikes()) + .dislikes(eventShortReview.getDislikes()) + .isLiked(eventShortReviewReaction.isLiked()) + .isDisliked(eventShortReviewReaction.isDisliked()) + .build(); + } + + public static EventShortReviewReaction toLike(User user, EventShortReview eventShortReview, boolean isLiked) { + return EventShortReviewReaction.builder() + .user(user) + .eventShortReview(eventShortReview) + .isLiked(isLiked) + .build(); + } + + public static EventShortReviewReaction toDislike(User user, EventShortReview eventShortReview, boolean isDisliked) { + return EventShortReviewReaction.builder() + .user(user) + .eventShortReview(eventShortReview) + .isDisliked(isDisliked) + .build(); + } +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionRequestDTO.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/dto/EventShortReviewReactionRequestDTO.java similarity index 68% rename from src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionRequestDTO.java rename to src/main/java/com/otakumap/domain/event_short_review_reaction/dto/EventShortReviewReactionRequestDTO.java index b378165c..58db806e 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionRequestDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/dto/EventShortReviewReactionRequestDTO.java @@ -1,10 +1,10 @@ -package com.otakumap.domain.event_reaction.dto; +package com.otakumap.domain.event_short_review_reaction.dto; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import lombok.Getter; -public class EventReactionRequestDTO { +public class EventShortReviewReactionRequestDTO { @Getter public static class ReactionRequestDTO { @Min(0) diff --git a/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionResponseDTO.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/dto/EventShortReviewReactionResponseDTO.java similarity index 78% rename from src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionResponseDTO.java rename to src/main/java/com/otakumap/domain/event_short_review_reaction/dto/EventShortReviewReactionResponseDTO.java index a98cab7e..b227c34f 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/dto/EventReactionResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/dto/EventShortReviewReactionResponseDTO.java @@ -1,11 +1,11 @@ -package com.otakumap.domain.event_reaction.dto; +package com.otakumap.domain.event_short_review_reaction.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -public class EventReactionResponseDTO { +public class EventShortReviewReactionResponseDTO { @Builder @Getter @NoArgsConstructor diff --git a/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java similarity index 80% rename from src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java rename to src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java index 9619c654..5dbff636 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/entity/EventReaction.java +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java @@ -1,4 +1,4 @@ -package com.otakumap.domain.event_reaction.entity; +package com.otakumap.domain.event_short_review_reaction.entity; import com.otakumap.domain.event_short_review.entity.EventShortReview; import com.otakumap.domain.user.entity.User; @@ -13,8 +13,8 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -@Table(name = "event_reaction", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "event_short_review_id"})}) -public class EventReaction extends BaseEntity { +@Table(name = "event_short_review_reaction", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "event_short_review_id"})}) +public class EventShortReviewReaction extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/com/otakumap/domain/event_short_review_reaction/repository/EventShortReviewReactionRepository.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/repository/EventShortReviewReactionRepository.java new file mode 100644 index 00000000..8c33ff76 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/repository/EventShortReviewReactionRepository.java @@ -0,0 +1,10 @@ +package com.otakumap.domain.event_short_review_reaction.repository; + +import com.otakumap.domain.event_short_review_reaction.entity.EventShortReviewReaction; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface EventShortReviewReactionRepository extends JpaRepository { + Optional findByUserIdAndEventShortReviewId(Long userId, Long eventShortReviewId); +} diff --git a/src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandService.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandService.java new file mode 100644 index 00000000..fd9c74b8 --- /dev/null +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandService.java @@ -0,0 +1,8 @@ +package com.otakumap.domain.event_short_review_reaction.service; + +import com.otakumap.domain.event_short_review_reaction.entity.EventShortReviewReaction; +import com.otakumap.domain.user.entity.User; + +public interface EventShortReviewReactionCommandService { + EventShortReviewReaction reactToReview(User user, Long reviewId, int reactionType); +} diff --git a/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandServiceImpl.java similarity index 50% rename from src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java rename to src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandServiceImpl.java index 8b00d767..3add02a6 100644 --- a/src/main/java/com/otakumap/domain/event_reaction/service/EventReactionCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/service/EventShortReviewReactionCommandServiceImpl.java @@ -1,8 +1,8 @@ -package com.otakumap.domain.event_reaction.service; +package com.otakumap.domain.event_short_review_reaction.service; -import com.otakumap.domain.event_reaction.converter.EventReactionConverter; -import com.otakumap.domain.event_reaction.entity.EventReaction; -import com.otakumap.domain.event_reaction.repository.EventReactionRepository; +import com.otakumap.domain.event_short_review_reaction.converter.EventShortReviewReactionConverter; +import com.otakumap.domain.event_short_review_reaction.entity.EventShortReviewReaction; +import com.otakumap.domain.event_short_review_reaction.repository.EventShortReviewReactionRepository; import com.otakumap.domain.event_short_review.entity.EventShortReview; import com.otakumap.domain.event_short_review.repository.EventShortReviewRepository; import com.otakumap.domain.user.entity.User; @@ -14,54 +14,54 @@ @Service @RequiredArgsConstructor -public class EventReactionCommandServiceImpl implements EventReactionCommandService { - private final EventReactionRepository eventReactionRepository; +public class EventShortReviewReactionCommandServiceImpl implements EventShortReviewReactionCommandService { + private final EventShortReviewReactionRepository eventShortReviewReactionRepository; private final EventShortReviewRepository eventShortReviewRepository; @Override @Transactional - public EventReaction reactToReview(User user, Long reviewId, int reactionType) { + public EventShortReviewReaction reactToReview(User user, Long reviewId, int reactionType) { EventShortReview eventShortReview = eventShortReviewRepository.findById(reviewId).orElseThrow(() -> new ReviewHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); - EventReaction eventReaction = eventReactionRepository.findByUserIdAndEventShortReviewId(user.getId(), reviewId).orElse(null); + EventShortReviewReaction eventShortReviewReaction = eventShortReviewReactionRepository.findByUserIdAndEventShortReviewId(user.getId(), reviewId).orElse(null); - if (eventReaction == null) { + if (eventShortReviewReaction == null) { if (reactionType == 0) { // dislike - eventReaction = EventReactionConverter.toDislike(user, eventShortReview, true); + eventShortReviewReaction = EventShortReviewReactionConverter.toDislike(user, eventShortReview, true); eventShortReview.updateDislikes(eventShortReview.getDislikes() + 1); } else { // like - eventReaction = EventReactionConverter.toLike(user, eventShortReview, true); + eventShortReviewReaction = EventShortReviewReactionConverter.toLike(user, eventShortReview, true); eventShortReview.updateLikes(eventShortReview.getLikes() + 1); } } else { if (reactionType == 0) { // dislike - if (!eventReaction.isDisliked()) { - eventReaction.updateDisliked(true); - eventReaction.updateLiked(false); + if (!eventShortReviewReaction.isDisliked()) { + eventShortReviewReaction.updateDisliked(true); + eventShortReviewReaction.updateLiked(false); eventShortReview.updateDislikes(eventShortReview.getDislikes() + 1); - if (eventReaction.isLiked()) { + if (eventShortReviewReaction.isLiked()) { eventShortReview.updateLikes(eventShortReview.getLikes() - 1); } } else { - eventReaction.updateDisliked(false); + eventShortReviewReaction.updateDisliked(false); eventShortReview.updateDislikes(eventShortReview.getDislikes() - 1); } } else { // like - if (!eventReaction.isLiked()) { - eventReaction.updateLiked(true); - eventReaction.updateDisliked(false); + if (!eventShortReviewReaction.isLiked()) { + eventShortReviewReaction.updateLiked(true); + eventShortReviewReaction.updateDisliked(false); eventShortReview.updateLikes(eventShortReview.getLikes() + 1); - if (eventReaction.isDisliked()) { + if (eventShortReviewReaction.isDisliked()) { eventShortReview.updateDislikes(eventShortReview.getDislikes() - 1); } } else { - eventReaction.updateLiked(false); + eventShortReviewReaction.updateLiked(false); eventShortReview.updateLikes(eventShortReview.getLikes() - 1); } } } eventShortReviewRepository.save(eventShortReview); - return eventReactionRepository.save(eventReaction); + return eventShortReviewReactionRepository.save(eventShortReviewReaction); } } \ No newline at end of file From 4c5f5be8fcef9c2cc86e4d36b069e3af497d0ec9 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 16:40:49 +0900 Subject: [PATCH 472/516] =?UTF-8?q?Refactor:=20cascade=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_short_review/entity/EventShortReview.java | 7 +++++++ .../entity/EventShortReviewReaction.java | 3 --- .../domain/place_short_review/entity/PlaceShortReview.java | 7 +++++++ .../otakumap/domain/user_reaction/entity/UserReaction.java | 3 --- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java index 8c3c682f..fb23a870 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java +++ b/src/main/java/com/otakumap/domain/event_short_review/entity/EventShortReview.java @@ -1,5 +1,6 @@ package com.otakumap.domain.event_short_review.entity; +import com.otakumap.domain.event_short_review_reaction.entity.EventShortReviewReaction; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.event.entity.Event; import com.otakumap.global.common.BaseEntity; @@ -9,6 +10,9 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @DynamicUpdate @@ -41,6 +45,9 @@ public class EventShortReview extends BaseEntity { @ColumnDefault("0") private Long dislikes; + @OneToMany(mappedBy = "eventShortReview", cascade = CascadeType.REMOVE, orphanRemoval = true) + private List reactions = new ArrayList<>(); + public void setContent(String content) { this.content = content; } diff --git a/src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java b/src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java index 5dbff636..2afa4180 100644 --- a/src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java +++ b/src/main/java/com/otakumap/domain/event_short_review_reaction/entity/EventShortReviewReaction.java @@ -5,8 +5,6 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; @Entity @Getter @@ -25,7 +23,6 @@ public class EventShortReviewReaction extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_short_review_id", nullable = false) - @OnDelete(action = OnDeleteAction.CASCADE) private EventShortReview eventShortReview; @Column(nullable = false) diff --git a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java index b9d4b292..d60655bd 100644 --- a/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java +++ b/src/main/java/com/otakumap/domain/place_short_review/entity/PlaceShortReview.java @@ -3,10 +3,14 @@ import com.otakumap.domain.mapping.PlaceAnimation; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user_reaction.entity.UserReaction; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @Builder @@ -41,6 +45,9 @@ public class PlaceShortReview extends BaseEntity { @JoinColumn(name = "place_animation_id") private PlaceAnimation placeAnimation; + @OneToMany(mappedBy = "placeShortReview", cascade = CascadeType.REMOVE, orphanRemoval = true) + private List reactions = new ArrayList<>(); + public void updateLikes(Long likes) { this.likes = likes; } public void updateDislikes(Long dislikes) { this.dislikes = dislikes; } diff --git a/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java b/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java index 6e5a46d8..b2bf1107 100644 --- a/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java +++ b/src/main/java/com/otakumap/domain/user_reaction/entity/UserReaction.java @@ -5,8 +5,6 @@ import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; @Entity @Getter @@ -25,7 +23,6 @@ public class UserReaction extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_short_review_id", nullable = false) - @OnDelete(action = OnDeleteAction.CASCADE) private PlaceShortReview placeShortReview; @Column(nullable = false) From d722dae66c436ceb6fab341dd44d81535d29d809 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 17:59:51 +0900 Subject: [PATCH 473/516] =?UTF-8?q?Feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/contoller/UserController.java | 7 +++++++ .../com/otakumap/domain/user/dto/UserRequestDTO.java | 11 +++++++++++ .../java/com/otakumap/domain/user/entity/User.java | 2 ++ .../domain/user/service/UserCommandService.java | 1 + .../domain/user/service/UserCommandServiceImpl.java | 10 +++++++--- 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index a2970419..832d5768 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -106,4 +106,11 @@ public ApiResponse changeEmail(@RequestBody @Valid UserRequestDTO.Change userCommandService.changeEmail(user, request); return ApiResponse.onSuccess("이메일 변경이 성공적으로 완료되었습니다."); } + + @PatchMapping("/password") + @Operation(summary = "비밀번호 변경 API", description = "비밀번호 변경을 위한 기능입니다.") + public ApiResponse changePassword(@RequestBody @Valid UserRequestDTO.ChangePasswordDTO request, @CurrentUser User user) { + userCommandService.changePassword(user, request); + return ApiResponse.onSuccess("비밀번호 변경이 성공적으로 완료되었습니다."); + } } diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 211a4263..962449d2 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -58,4 +58,15 @@ public static class ChangeEmailDTO { @Email(message = "이메일 형식이 올바르지 않습니다.") String email; } + + @Getter + public static class ChangePasswordDTO { + @NotBlank(message = "비밀번호 입력은 필수 입니다.") + @Schema(description = "password", example = "otakumap1234!") + @Pattern( + regexp = "^(?!.*(\\d)\\1{2})(?=(.*[A-Za-z]){1})(?=(.*\\d){1})(?!.*\\s).{10,}$|^(?!.*(\\d)\\1{2})(?=(.*[A-Za-z]){1})(?=(.*[^A-Za-z0-9]){1})(?!.*\\s).{10,}$|^(?!.*(\\d)\\1{2})(?=(.*\\d){1})(?=(.*[^A-Za-z0-9]){1})(?!.*\\s).{10,}$", + message = "비밀번호는 영문, 숫자, 특수문자 중 2종류 이상을 조합하여 10자리 이상이어야 하며, 동일한 숫자 3개 이상을 연속해서 사용할 수 없습니다." + ) + String password; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 882c32f7..8adb6334 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -101,4 +101,6 @@ public void setProfileImage(Image image) { public void updateEmail(String email) { this.email = email; } + + public void updatePassword(String password) { this.password = password; } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java index d1d0c99c..69b4f05e 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandService.java @@ -11,4 +11,5 @@ public interface UserCommandService { void resetPassword(UserRequestDTO.ResetPasswordDTO request); String updateProfileImage(User user, MultipartFile file); void changeEmail(User user, UserRequestDTO.ChangeEmailDTO request); + void changePassword(User user, UserRequestDTO.ChangePasswordDTO request); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 14471238..e07147df 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -11,15 +11,12 @@ import com.otakumap.global.apiPayload.exception.handler.UserHandler; import com.otakumap.global.util.EmailUtil; import com.otakumap.global.util.RedisUtil; -import jakarta.mail.MessagingException; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.concurrent.TimeUnit; - @Service @RequiredArgsConstructor public class UserCommandServiceImpl implements UserCommandService { @@ -95,4 +92,11 @@ public void changeEmail(User user, UserRequestDTO.ChangeEmailDTO request) { user.updateEmail(request.getEmail()); userRepository.save(user); } + + @Override + @Transactional + public void changePassword(User user, UserRequestDTO.ChangePasswordDTO request) { + user.updatePassword(passwordEncoder.encode(request.getPassword())); + userRepository.save(user); + } } \ No newline at end of file From e715f9d21dcced26b7e66a2df15060532675b61c Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 18:13:52 +0900 Subject: [PATCH 474/516] Refactor: Use encodePassword method for password updates --- src/main/java/com/otakumap/domain/user/entity/User.java | 2 -- .../otakumap/domain/user/service/UserCommandServiceImpl.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 8adb6334..882c32f7 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -101,6 +101,4 @@ public void setProfileImage(Image image) { public void updateEmail(String email) { this.email = email; } - - public void updatePassword(String password) { this.password = password; } } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index e07147df..c9ab5508 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -96,7 +96,7 @@ public void changeEmail(User user, UserRequestDTO.ChangeEmailDTO request) { @Override @Transactional public void changePassword(User user, UserRequestDTO.ChangePasswordDTO request) { - user.updatePassword(passwordEncoder.encode(request.getPassword())); + user.encodePassword(passwordEncoder.encode(request.getPassword())); userRepository.save(user); } } \ No newline at end of file From caa5fa0a1bc43aaffc875ca76cd3d08988b41ca4 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 19:24:06 +0900 Subject: [PATCH 475/516] =?UTF-8?q?Fix:=20=EA=B2=B0=EC=A0=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20api=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/dto/PaymentVerifyRequest.java | 4 +- .../service/PaymentCommandServiceImpl.java | 42 +++++++++---------- .../otakumap/domain/point/entity/Point.java | 5 ++- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java index cbdbcac2..732267cd 100644 --- a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java +++ b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java @@ -3,6 +3,8 @@ import jakarta.validation.constraints.NotNull; import lombok.Getter; +import java.math.BigDecimal; + @Getter public class PaymentVerifyRequest { @NotNull(message = "imp_uid는 필수입니다.") @@ -10,5 +12,5 @@ public class PaymentVerifyRequest { @NotNull(message = "merchant_uid는 필수입니다.") String merchantUid; @NotNull(message = "amount는 필수입니다.") - Long amount; + BigDecimal amount; } diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java index 49f801f4..3e97bf55 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -4,7 +4,6 @@ import com.otakumap.domain.payment.entity.UserPayment; import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.payment.repository.PaymentRepository; -import com.otakumap.domain.point.converter.PointConverter; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.point.repository.PointRepository; import com.otakumap.domain.user.entity.User; @@ -29,7 +28,6 @@ public class PaymentCommandServiceImpl implements PaymentCommandService { private final IamportClient iamportClient; private final PointRepository pointRepository; private final PaymentRepository paymentRepository; - //private final PointConverter pointConverter; @Transactional public void verifyPayment(User user, PaymentVerifyRequest request) throws IOException, IamportResponseException { @@ -37,6 +35,8 @@ public void verifyPayment(User user, PaymentVerifyRequest request) throws IOExce IamportResponse paymentResponse = iamportClient.paymentByImpUid(request.getImpUid()); Payment payment = paymentResponse.getResponse(); + System.out.println("✅ Payment 정보: " + payment.getImpUid() + " " + payment.getMerchantUid() + " " + payment.getAmount()); + if (payment == null) { throw new PaymentHandler(ErrorStatus.PAYMENT_NOT_FOUND); } @@ -56,7 +56,19 @@ public void verifyPayment(User user, PaymentVerifyRequest request) throws IOExce throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); } - // 결제 정보 저장 + + // 포인트 먼저 생성 후 저장 + Point point = new Point( + Long.valueOf(String.valueOf(payment.getAmount())), // 충전된 포인트 + LocalDateTime.now(), // 충전 시간 + PaymentStatus.PAID, // 상태 설정 + user, // 사용자 정보 + null // 🔥 UserPayment는 아직 생성되지 않았으므로 null로 설정 + ); + + point = pointRepository.save(point); + + // 포인트를 포함한 UserPayment 생성 및 저장 UserPayment userPayment = UserPayment.builder() .user(user) .impUid(payment.getImpUid()) @@ -64,28 +76,16 @@ public void verifyPayment(User user, PaymentVerifyRequest request) throws IOExce .amount(payment.getAmount().longValue()) .verifiedAt(LocalDateTime.now()) .status(PaymentStatus.PAID) + .point(point) .build(); - paymentRepository.save(userPayment); + userPayment = paymentRepository.save(userPayment); - System.out.println("✅ Payment 정보: " + payment); - System.out.println("✅ UserPayment 정보: " + userPayment); - - // 포인트 저장 - Point point = new Point( - Long.valueOf(String.valueOf(payment.getAmount())), // 충전된 포인트 - LocalDateTime.now(), // 충전 시간 - PaymentStatus.PAID, // 상태 설정 - user, // 사용자 정보 - userPayment // 결제 정보 - ); + // Point에 UserPayment 설정 후 다시 저장 + point.setUserPayment(userPayment); + pointRepository.save(point); + System.out.println("✅ UserPayment 정보: " + userPayment); System.out.println("✅ 생성된 Point 정보: " + point); - - pointRepository.save(point); // 포인트 저장 - - // 결제 정보와 포인트 정보를 연결 - userPayment.setPoint(point); - paymentRepository.save(userPayment); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index b635a14b..ef780cfd 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -55,7 +55,7 @@ public class Point extends BaseEntity { private List transactionList = new ArrayList<>(); @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_payment_id", nullable = false) + @JoinColumn(name = "user_payment_id", nullable = true) private UserPayment userPayment; public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User user, UserPayment userPayment) { @@ -66,4 +66,7 @@ public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User use this.userPayment = userPayment; // userPayment 필드를 추가로 설정 } + public void setUserPayment(UserPayment userPayment) { + this.userPayment = userPayment; + } } \ No newline at end of file From 22dece020f68b47f90966dce76157aa54714f734 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 19:31:41 +0900 Subject: [PATCH 476/516] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B3=B5=EB=B0=B1&import=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/payment/service/PaymentCommandServiceImpl.java | 1 - src/main/java/com/otakumap/domain/point/entity/Point.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java index 3e97bf55..57243db5 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -56,7 +56,6 @@ public void verifyPayment(User user, PaymentVerifyRequest request) throws IOExce throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); } - // 포인트 먼저 생성 후 저장 Point point = new Point( Long.valueOf(String.valueOf(payment.getAmount())), // 충전된 포인트 diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index ef780cfd..c87997df 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -12,7 +12,6 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; -import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; From 748259ad96e30b83ae81d2884764ddc2dfd1c090 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Wed, 19 Feb 2025 20:02:46 +0900 Subject: [PATCH 477/516] =?UTF-8?q?Chore:=20=EC=93=B0=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/point/entity/Point.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index e5e0ff0f..54954b30 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -63,11 +63,6 @@ public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User use this.status = status; this.user = user; } - - public Point(User user, String merchantUid, Long point) { - this.user = user; - this.merchantUid = merchantUid; - } public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User user, UserPayment userPayment) { this.point = point; From 8ec754d7aeba98dc6c7334356d55e24b76e6f3d8 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Wed, 19 Feb 2025 20:42:59 +0900 Subject: [PATCH 478/516] =?UTF-8?q?Feat:=20=EC=9C=A0=EC=A0=80=20=EA=B0=9C?= =?UTF-8?q?=EC=9D=B8=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=8B=9C=20=ED=9B=84=EA=B8=B0=20=ED=9B=84?= =?UTF-8?q?=EC=9B=90=EA=B8=88=20=EB=82=B4=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/repository/ReviewRepositoryImpl.java | 5 +++++ src/main/java/com/otakumap/domain/user/entity/User.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index f587af45..268cd39b 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -22,6 +22,7 @@ import com.otakumap.domain.transaction.enums.TransactionType; import com.otakumap.domain.transaction.repository.TransactionRepository; import com.otakumap.domain.user.entity.User; +import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.apiPayload.exception.handler.SearchHandler; @@ -52,6 +53,7 @@ public class ReviewRepositoryImpl implements ReviewRepositoryCustom { private final PointRepository pointRepository; private final PlaceReviewRepository placeReviewRepository; private final TransactionRepository transactionRepository; + private final UserRepository userRepository; @Override public Page getReviewsByKeyword(String keyword, int page, int size, String sort) { @@ -223,6 +225,9 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( transactionRepository.save(new Transaction(buyerPoint, TransactionType.USAGE, priceInt, null, (PlaceReview) review)); transactionRepository.save(new Transaction(sellerPoint, TransactionType.EARNING, priceInt, null, (PlaceReview) review)); } + // 판매자 후원금 내역에 판매 금액만큼 추가하여 저장 + seller.addEarnings(priceInt); + userRepository.save(seller); return ReviewResponseDTO.PurchaseReviewDTO.builder() .remainingPoints(remainingPoints) .build(); diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 882c32f7..2b4447e7 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -101,4 +101,8 @@ public void setProfileImage(Image image) { public void updateEmail(String email) { this.email = email; } + + public void addEarnings(int price) { + this.donation += price; + } } From 66dc88f19c90b9a732874c9847ef36b1504e21dd Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 23:48:51 +0900 Subject: [PATCH 479/516] =?UTF-8?q?Refactor:=20=EB=82=B4=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A6=AC=EB=B7=B0=EB=8F=84=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventReviewRepository.java | 4 ++ .../repository/PlaceReviewRepository.java | 4 +- .../domain/user/contoller/UserController.java | 3 +- .../domain/user/converter/UserConverter.java | 24 +++++-- .../domain/user/dto/UserResponseDTO.java | 1 + .../domain/user/service/UserQueryService.java | 4 +- .../user/service/UserQueryServiceImpl.java | 67 ++++++++++++++++++- .../apiPayload/code/status/ErrorStatus.java | 1 + 8 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 008f1759..e6da3a3a 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -4,11 +4,13 @@ import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface EventReviewRepository extends JpaRepository { @@ -18,5 +20,7 @@ public interface EventReviewRepository extends JpaRepository Optional findUserByRouteId(@Param("routeId") Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); +// Page findAllByUserId(Long userId, PageRequest pageRequest); + List findAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 25155da5..de1ce3a8 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -8,11 +8,13 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { - Page findAllByUserId(Long userId, PageRequest pageRequest); +// Page findAllByUserId(Long userId, PageRequest pageRequest); + List findAllByUserId(Long userId); void deleteAllByUserId(Long userId); Optional findByRouteId(Long routeId); @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.route.id = :routeId") diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index 832d5768..407ddd43 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -1,7 +1,6 @@ package com.otakumap.domain.user.contoller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.service.PlaceReviewCommandService; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; @@ -72,7 +71,7 @@ public ApiResponse updateNotificationSettings( public ApiResponse getMyReviews( @CurrentUser User user, @CheckPage @RequestParam(name = "page") Integer page, @RequestParam(name = "sort", defaultValue = "createdAt") String sort) { - Page reviews = userQueryService.getMyReviews(user, page, sort); + Page reviews = userQueryService.getMyReviews(user, page, sort); return ApiResponse.onSuccess(UserConverter.reviewListDTO(reviews)); } diff --git a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java index 85d04b06..be51143c 100644 --- a/src/main/java/com/otakumap/domain/user/converter/UserConverter.java +++ b/src/main/java/com/otakumap/domain/user/converter/UserConverter.java @@ -1,6 +1,7 @@ package com.otakumap.domain.user.converter; import com.otakumap.domain.auth.dto.*; +import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; @@ -12,7 +13,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; public class UserConverter { public static User toUser(AuthRequestDTO.SignupDTO request) { @@ -107,20 +107,32 @@ public static AuthResponseDTO.FindIdResultDTO toFindIdResultDTO(String userId) { .build(); } - public static UserResponseDTO.UserReviewDTO reviewDTO(PlaceReview review) { + public static UserResponseDTO.UserReviewDTO reviewDTO(PlaceReview review, String imageUrl) { return UserResponseDTO.UserReviewDTO.builder() .reviewId(review.getId()) + .reviewType("place") .title(review.getTitle()) .content(review.getContent()) - .thumbnail(review.getImages().get(0) == null ? null : review.getImages().get(0).getFileUrl()) // 이미지 여러 개면 수정 -> 나중에 수정 필요! + .thumbnail(imageUrl) .views(review.getView()) .createdAt(review.getCreatedAt().toLocalDate()) .build(); } - public static UserResponseDTO.UserReviewListDTO reviewListDTO(Page reviews) { - List userReviewDTOS = reviews.stream() - .map(UserConverter::reviewDTO).collect(Collectors.toList()); + public static UserResponseDTO.UserReviewDTO reviewDTO(EventReview review, String imageUrl) { + return UserResponseDTO.UserReviewDTO.builder() + .reviewId(review.getId()) + .reviewType("event") + .title(review.getTitle()) + .content(review.getContent()) + .thumbnail(imageUrl) + .views(review.getView()) + .createdAt(review.getCreatedAt().toLocalDate()) + .build(); + } + + public static UserResponseDTO.UserReviewListDTO reviewListDTO(Page reviews) { + List userReviewDTOS = reviews.getContent(); return UserResponseDTO.UserReviewListDTO.builder() .reviews(userReviewDTOS) diff --git a/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java index 4d4a816a..582b577d 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserResponseDTO.java @@ -41,6 +41,7 @@ public static class UserReviewListDTO { @AllArgsConstructor public static class UserReviewDTO { private Long reviewId; + private String reviewType; private String title; private String content; private String thumbnail; diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java index dfe3246c..a8a6c55c 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryService.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryService.java @@ -1,11 +1,11 @@ package com.otakumap.domain.user.service; -import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; public interface UserQueryService { User getUserByEmail(String email); User getUserInfo(Long userId); - Page getMyReviews(User user, Integer page, String sort); + Page getMyReviews(User user, Integer page, String sort); } diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index 679a9ef2..34e498ab 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -1,22 +1,32 @@ package com.otakumap.domain.user.service; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.user.converter.UserConverter; +import com.otakumap.domain.user.dto.UserResponseDTO; import com.otakumap.domain.user.entity.User; import com.otakumap.domain.user.repository.UserRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.AuthHandler; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; + @Service @RequiredArgsConstructor public class UserQueryServiceImpl implements UserQueryService { private final UserRepository userRepository; private final PlaceReviewRepository placeReviewRepository; + private final EventReviewRepository eventReviewRepository; @Override public User getUserByEmail(String email) { @@ -29,11 +39,64 @@ public User getUserInfo(Long userId) { } @Override - public Page getMyReviews(User user, Integer page, String sort) { + public Page getMyReviews(User user, Integer page, String sort) { Sort sortOrder = Sort.by(Sort.Order.desc("createdAt")); if ("views".equals(sort)) { sortOrder = Sort.by(Sort.Order.desc("view"), Sort.Order.desc("createdAt")); } - return placeReviewRepository.findAllByUserId(user.getId(), PageRequest.of(page - 1, 3, sortOrder)); + + // 전체 데이터를 먼저 가져옵니다 + List placeReviews = placeReviewRepository.findAllByUserId(user.getId()); + List eventReviews = eventReviewRepository.findAllByUserId(user.getId()); + + List reviews = new ArrayList<>(); + + // PlaceReview 처리 + placeReviews.forEach(review -> reviews.add( + UserConverter.reviewDTO(review, getImageUrl(review)) + )); + + // EventReview 처리 + eventReviews.forEach(review -> reviews.add( + UserConverter.reviewDTO(review, getImageUrl(review)) + )); + + // sort 파라미터에 따른 정렬 + if ("views".equals(sort)) { + reviews.sort((r1, r2) -> { + int viewCompare = r2.getViews().compareTo(r1.getViews()); + return viewCompare != 0 ? viewCompare : r2.getCreatedAt().compareTo(r1.getCreatedAt()); + }); + } else { + reviews.sort((r1, r2) -> r2.getCreatedAt().compareTo(r1.getCreatedAt())); + } + + // Pagination + int pageSize = 3; + int totalSize = reviews.size(); + int totalPages = (int) Math.ceil((double) totalSize / pageSize); + + if (page > totalPages) { + throw new ReviewHandler(ErrorStatus.PAGE_NOT_FOUND); + } + + int start = (page - 1) * pageSize; + int end = Math.min(start + pageSize, totalSize); + + List paginatedReviews = reviews.subList(start, end); + + return new PageImpl<>(paginatedReviews, PageRequest.of(page - 1, pageSize, sortOrder), totalSize); + } + + private String getImageUrl(PlaceReview review) { + return review.getImages() != null && !review.getImages().isEmpty() + ? review.getImages().get(0).getFileUrl() + : null; + } + + private String getImageUrl(EventReview review) { + return review.getImages() != null && !review.getImages().isEmpty() + ? review.getImages().get(0).getFileUrl() + : null; } } diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index f0daf9f2..9e3643ab 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -99,6 +99,7 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_REVIEW_TYPE(HttpStatus.BAD_REQUEST, "REVIEW4001", "유효하지 않은 후기 타입입니다."), INVALID_REVIEW_ID(HttpStatus.BAD_REQUEST, "REVIEW4002", "이벤트 후기와 장소 후기에 모두 존재하지 않는 후기 id 입니다."), REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4003", "존재하지 않는 후기입니다."), + PAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "REVIEW4004", "페이지를 찾을 수 없습니다."), // 이미지 관련 에러 INVALID_FOLDER(HttpStatus.BAD_REQUEST, "IMAGE4001", "유효하지 않은 폴더입니다."), From 45d4d5d2e6c8d7277f47696802590855ea05ba41 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 23:49:54 +0900 Subject: [PATCH 480/516] =?UTF-8?q?Refactor:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_review/repository/EventReviewRepository.java | 2 -- .../domain/place_review/repository/PlaceReviewRepository.java | 1 - .../com/otakumap/domain/user/service/UserQueryServiceImpl.java | 1 - 3 files changed, 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index e6da3a3a..6688a8ed 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -4,7 +4,6 @@ import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -20,7 +19,6 @@ public interface EventReviewRepository extends JpaRepository Optional findUserByRouteId(@Param("routeId") Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); -// Page findAllByUserId(Long userId, PageRequest pageRequest); List findAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index de1ce3a8..dbf028d8 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -13,7 +13,6 @@ public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { -// Page findAllByUserId(Long userId, PageRequest pageRequest); List findAllByUserId(Long userId); void deleteAllByUserId(Long userId); Optional findByRouteId(Long routeId); diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index 34e498ab..22409045 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -45,7 +45,6 @@ public Page getMyReviews(User user, Integer page, sortOrder = Sort.by(Sort.Order.desc("view"), Sort.Order.desc("createdAt")); } - // 전체 데이터를 먼저 가져옵니다 List placeReviews = placeReviewRepository.findAllByUserId(user.getId()); List eventReviews = eventReviewRepository.findAllByUserId(user.getId()); From ed319b42125c2400b1b7b9d082c61d1b5b9d99e7 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 23:56:18 +0900 Subject: [PATCH 481/516] =?UTF-8?q?Refactor:=20=EB=82=B4=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=84=EC=B2=B4=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A6=AC=EB=B7=B0=EB=8F=84=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/repository/EventReviewRepository.java | 1 + .../event_review/service/EventReviewCommandService.java | 1 + .../service/EventReviewCommandServiceImpl.java | 7 +++++++ .../com/otakumap/domain/user/contoller/UserController.java | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 6688a8ed..699caa22 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -20,5 +20,6 @@ public interface EventReviewRepository extends JpaRepository @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); List findAllByUserId(Long userId); + void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java index b0c3cbf1..e72e0627 100644 --- a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandService.java @@ -5,4 +5,5 @@ public interface EventReviewCommandService { Page getEventReviews(Long eventId, Integer page); + void deleteAllByUserId(Long userId); } diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java index 7151a0ac..308f65bf 100644 --- a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java @@ -6,6 +6,7 @@ import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -23,4 +24,10 @@ public Page getEventReviews(Long eventId, Integer page) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); return eventReviewRepository.findAllByEvent(event, PageRequest.of(page, 4)); } + + @Override + @Transactional + public void deleteAllByUserId(Long userId) { + eventReviewRepository.deleteAllByUserId(userId); + } } diff --git a/src/main/java/com/otakumap/domain/user/contoller/UserController.java b/src/main/java/com/otakumap/domain/user/contoller/UserController.java index 407ddd43..2c9c4f37 100644 --- a/src/main/java/com/otakumap/domain/user/contoller/UserController.java +++ b/src/main/java/com/otakumap/domain/user/contoller/UserController.java @@ -1,6 +1,7 @@ package com.otakumap.domain.user.contoller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.event_review.service.EventReviewCommandService; import com.otakumap.domain.place_review.service.PlaceReviewCommandService; import com.otakumap.domain.user.converter.UserConverter; import com.otakumap.domain.user.dto.UserRequestDTO; @@ -28,6 +29,7 @@ public class UserController { private final UserQueryService userQueryService; private final UserCommandService userCommandService; private final PlaceReviewCommandService placeReviewCommandService; + private final EventReviewCommandService eventReviewCommandService; @GetMapping @Operation(summary = "회원 정보 조회 API", description = "회원 정보를 조회합니다.") @@ -86,6 +88,7 @@ public ApiResponse resetPassword(@RequestBody @Valid UserRequestDTO.Rese @Operation(summary = "내가 작성한 후기 삭제", description = "내가 작성한 모든 후기를 삭제합니다.") public ApiResponse deleteAllReviews(@CurrentUser User user) { placeReviewCommandService.deleteAllByUserId(user.getId()); + eventReviewCommandService.deleteAllByUserId(user.getId()); return ApiResponse.onSuccess("모든 후기가 성공적으로 삭제되었습니다."); } From ef1c70f5ef01f04e2664213185204ee2eb4d0d3d Mon Sep 17 00:00:00 2001 From: haerxeong Date: Wed, 19 Feb 2025 23:58:49 +0900 Subject: [PATCH 482/516] =?UTF-8?q?Refactor:=20=EB=82=B4=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EB=B9=84=EC=96=B4=EC=9E=88=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/user/service/UserQueryServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java index 22409045..3eb7c6c2 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserQueryServiceImpl.java @@ -19,6 +19,7 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @Service @@ -60,6 +61,11 @@ public Page getMyReviews(User user, Integer page, UserConverter.reviewDTO(review, getImageUrl(review)) )); + // 리뷰가 없는 경우 빈 페이지 반환 + if (reviews.isEmpty()) { + return new PageImpl<>(Collections.emptyList(), PageRequest.of(page - 1, 3, sortOrder), 0); + } + // sort 파라미터에 따른 정렬 if ("views".equals(sort)) { reviews.sort((r1, r2) -> { From 82b847a77ab9e871fbcb17395c9deb3eeae7cf53 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 20 Feb 2025 03:49:31 +0900 Subject: [PATCH 483/516] =?UTF-8?q?Refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20request=EC=97=90=20=EC=97=B4=EB=9E=8C=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/reviews/dto/ReviewRequestDTO.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java index bb725425..eeb2796f 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.Size; import lombok.Getter; +import java.beans.Visibility; import java.util.List; public class ReviewRequestDTO { @@ -23,6 +24,9 @@ public static class CreateDTO { @NotNull(message = "애니메이션 id를 입력해주세요.") private Long animeId; + @NotBlank + private Visibility visibility; + @Size(min = 1, message = "루트 아이템은 최소 1개 이상 필요합니다.") private List routeItems; } @@ -44,4 +48,8 @@ public static class RouteDTO { @NotNull(message = "order를 입력해주세요.") private Integer order; } + + public enum Visibility { + PUBLIC, PURCHASERS_ONLY + } } From db6faabb4341c2318e5dd59e7a69dc3277527d70 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 20 Feb 2025 04:07:47 +0900 Subject: [PATCH 484/516] =?UTF-8?q?Feat:=20=ED=9B=84=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=95=8C=EB=A6=BC=20=EC=83=9D=EC=84=B1=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/NotificationCommandService.java | 2 ++ .../NotificationCommandServiceImpl.java | 23 +++++++++++++++++++ .../service/ReviewCommandServiceImpl.java | 3 +++ 3 files changed, 28 insertions(+) diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java index 64d28d4e..0dfe86a2 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandService.java @@ -1,9 +1,11 @@ package com.otakumap.domain.notification.service; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.user.entity.User; public interface NotificationCommandService { void markAsRead(Long userId, Long notificationId); void notifyEventStarted(User user, Long eventId); void notifyRootSaved(User user, Long routeId, int rootCount); + void notifyReviewPurchased(User purchaser, Long reviewId, ReviewType type); } diff --git a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java index 847373eb..0b55ff8d 100644 --- a/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/notification/service/NotificationCommandServiceImpl.java @@ -2,10 +2,13 @@ import com.otakumap.domain.event.entity.Event; import com.otakumap.domain.event.repository.EventRepository; +import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.notification.converter.NotificationConverter; import com.otakumap.domain.notification.entity.Notification; import com.otakumap.domain.notification.entity.enums.NotificationType; import com.otakumap.domain.notification.repository.NotificationRepository; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route.repository.RouteRepository; import com.otakumap.domain.user.entity.User; @@ -21,6 +24,8 @@ public class NotificationCommandServiceImpl implements NotificationCommandServic private final NotificationRepository notificationRepository; private final EventRepository eventRepository; private final RouteRepository routeRepository; + private final PlaceReviewRepository placeReviewRepository; + private final EventReviewRepository eventReviewRepository; @Override @Transactional @@ -61,4 +66,22 @@ public void notifyRootSaved(User user, Long routeId, int rootCount) { String url = "/api/routes/" + routeId; notificationRepository.save(NotificationConverter.toNotification(user, NotificationType.ROOT_SAVED, message, url)); } + + @Override + @Transactional + public void notifyReviewPurchased(User purchaser, Long reviewId, ReviewType type) { + // 알림 전송받을 유저 + User user = null; + if (type == ReviewType.EVENT) { + user = eventReviewRepository.findUserById(reviewId); + } else if (type == ReviewType.PLACE) { + user = placeReviewRepository.findUserById(reviewId); + } else { + throw new NotificationHandler(ErrorStatus.INVALID_REVIEW_TYPE); + } + + String message = purchaser.getNickname() + "님이 회원님을 후원했어요."; + String url = "/api/reviews/" + reviewId + "?type=" + type; + notificationRepository.save(NotificationConverter.toNotification(user, NotificationType.POST_SUPPORTED, message, url)); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 09d07990..1da93792 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -8,6 +8,7 @@ import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.mapping.EventReviewPlace; import com.otakumap.domain.mapping.PlaceReviewPlace; +import com.otakumap.domain.notification.service.NotificationCommandService; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; import com.otakumap.domain.place_animation.repository.PlaceAnimationRepository; @@ -52,6 +53,7 @@ public class ReviewCommandServiceImpl implements ReviewCommandService { private final PlaceAnimationRepository placeAnimationRepository; private final PlaceReviewPlaceRepository placeReviewPlaceRepository; private final EventReviewPlaceRepository eventReviewPlaceRepository; + private final NotificationCommandService notificationCommandService; @Override @Transactional @@ -141,6 +143,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO @Override public ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type) { + notificationCommandService.notifyReviewPurchased(user, reviewId, type); return reviewRepositoryCustom.purchaseReview(user, reviewId, type); } } \ No newline at end of file From 9568ad61ea8b652f0dc880bb112d23f4c754b865 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 20 Feb 2025 04:20:06 +0900 Subject: [PATCH 485/516] =?UTF-8?q?Feat:=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=97=90=20=EC=97=B4=EB=9E=8C=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/reviews/converter/ReviewConverter.java | 6 ++++-- .../com/otakumap/domain/reviews/dto/ReviewRequestDTO.java | 2 +- .../domain/reviews/service/ReviewCommandServiceImpl.java | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index f6c3c40d..8ebb0f79 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -133,23 +133,25 @@ public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewI .build(); } - public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, Route route) { + public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, Route route, Long price) { return EventReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) .route(route) + .price(price) .build(); } - public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Route route) { + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Route route, Long price) { return PlaceReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) .route(route) + .price(price) .build(); } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java index eeb2796f..2ea019f4 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -24,7 +24,7 @@ public static class CreateDTO { @NotNull(message = "애니메이션 id를 입력해주세요.") private Long animeId; - @NotBlank + @NotNull(message = "열람 범위를 입력해주세요.") private Visibility visibility; @Size(min = 1, message = "루트 아이템은 최소 1개 이상 필요합니다.") diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 1da93792..a662178b 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -101,9 +101,11 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND))) .collect(Collectors.toList()); + Long price = (request.getVisibility() == ReviewRequestDTO.Visibility.PUBLIC) ? 0L : 500L; + if (request.getReviewType() == ReviewType.PLACE) { // 먼저 PlaceReview를 저장 - PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, route); + PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, route, price); placeReview.setAnimation(animation); placeReview = placeReviewRepository.save(placeReview); @@ -121,7 +123,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO return ReviewConverter.toCreatedReviewDTO(placeReview.getId(), placeReview.getTitle()); } else if (request.getReviewType() == ReviewType.EVENT) { // 먼저 EventReview를 저장 - EventReview eventReview = ReviewConverter.toEventReview(request, user, route); + EventReview eventReview = ReviewConverter.toEventReview(request, user, route, price); eventReview.setAnimation(animation); eventReview = eventReviewRepository.save(eventReview); From 4d984c550688f76c0effc9726f1da3c803cdfbe5 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 20 Feb 2025 04:21:03 +0900 Subject: [PATCH 486/516] =?UTF-8?q?Refactor:=20import=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java index 2ea019f4..53917aee 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewRequestDTO.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.Size; import lombok.Getter; -import java.beans.Visibility; import java.util.List; public class ReviewRequestDTO { From 8051b5e6342608663bb83f5e2cdb6efb86a5faa5 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 04:21:20 +0900 Subject: [PATCH 487/516] =?UTF-8?q?Fix:=20=EA=B2=B0=EC=A0=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20&=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=A9?= =?UTF-8?q?=EC=A0=84=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/order/dto/OrderDto.java | 19 +++ .../otakumap/domain/order/entity/Order.java | 28 ++++ .../order/repository/OrderRepository.java | 7 + .../payment/controller/PaymentController.java | 33 +++-- .../payment/dto/PaymentVerifyRequest.java | 16 --- .../domain/payment/entity/UserPayment.java | 53 ------- .../payment/repository/PaymentRepository.java | 13 -- .../service/PaymentCommandService.java | 11 +- .../service/PaymentCommandServiceImpl.java | 136 ++++++++++-------- .../point/controller/PointController.java | 22 +-- .../point/converter/PointConverter.java | 29 ++-- .../otakumap/domain/point/entity/Point.java | 27 ++-- .../point/repository/PointRepository.java | 4 +- .../point/service/PointCommandService.java | 1 - .../service/PointCommandServiceImpl.java | 27 ---- .../dto/TransactionResponseDTO.java | 36 +++++ .../transaction/entity/Transaction.java | 6 + 17 files changed, 226 insertions(+), 242 deletions(-) create mode 100644 src/main/java/com/otakumap/domain/order/dto/OrderDto.java create mode 100644 src/main/java/com/otakumap/domain/order/entity/Order.java create mode 100644 src/main/java/com/otakumap/domain/order/repository/OrderRepository.java delete mode 100644 src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java delete mode 100644 src/main/java/com/otakumap/domain/payment/entity/UserPayment.java delete mode 100644 src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java diff --git a/src/main/java/com/otakumap/domain/order/dto/OrderDto.java b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java new file mode 100644 index 00000000..f8240f6d --- /dev/null +++ b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java @@ -0,0 +1,19 @@ +package com.otakumap.domain.order.dto; + +import com.otakumap.domain.order.entity.Order; +import lombok.Getter; + +@Getter +public class OrderDto { + Long price; + String impUid; + String merchantUid; + + public Order toEntity() { + return Order.builder() + .price(price) + .impUid(impUid) + .merchantUid(merchantUid) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/entity/Order.java b/src/main/java/com/otakumap/domain/order/entity/Order.java new file mode 100644 index 00000000..1b5297da --- /dev/null +++ b/src/main/java/com/otakumap/domain/order/entity/Order.java @@ -0,0 +1,28 @@ +package com.otakumap.domain.order.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long price; + + @Column(nullable = false) + private String impUid; + + @Column(nullable = false) + private String merchantUid; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java new file mode 100644 index 00000000..52ae71dc --- /dev/null +++ b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.order.repository; + +import com.otakumap.domain.order.entity.Order; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java b/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java index 2a74e295..9a5b47dd 100644 --- a/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/otakumap/domain/payment/controller/PaymentController.java @@ -1,13 +1,11 @@ package com.otakumap.domain.payment.controller; -import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.payment.dto.PaymentVerifyRequest; import com.otakumap.domain.payment.service.PaymentCommandService; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.ApiResponse; import com.siot.IamportRestClient.exception.IamportResponseException; +import com.siot.IamportRestClient.response.IamportResponse; +import com.siot.IamportRestClient.response.Payment; import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -18,23 +16,24 @@ @RestController @RequestMapping("/api/payments") @RequiredArgsConstructor -@Validated @Slf4j +@Validated +@Tag(name = "Payments", description = "결제 API") public class PaymentController { private final PaymentCommandService paymentCommandService; - @Operation(summary = "결제 검증", description = "결제가 제대로 진행됐는지 검증합니다.") - @PostMapping("/verify") - public ApiResponse verifyPayment( - @Valid @RequestBody PaymentVerifyRequest request, - @CurrentUser User user - ) throws IamportResponseException, IOException { - - // 결제 검증 서비스 호출 - paymentCommandService.verifyPayment(user, request); + @Operation(summary = "아임포트 결제 정보 검증", description = "아임포트 결제 정보를 검증합니다.") + @PostMapping("/verify/{imp_uid}") + public IamportResponse validateIamport(@PathVariable String imp_uid) throws IamportResponseException, IOException { + log.info("imp_uid: {}", imp_uid); + log.info("validateIamport"); + return paymentCommandService.validateIamport(imp_uid); + } - // 성공적인 응답 반환 - return ApiResponse.onSuccess("결제가 검증되었습니다."); + @Operation(summary = "결제 취소", description = "결제를 취소합니다.") + @PostMapping("/cancel/{imp_uid}") + public IamportResponse cancelPayment(@PathVariable String imp_uid) throws IamportResponseException, IOException { + return paymentCommandService.cancelPayment(imp_uid); } } diff --git a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java b/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java deleted file mode 100644 index 732267cd..00000000 --- a/src/main/java/com/otakumap/domain/payment/dto/PaymentVerifyRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.otakumap.domain.payment.dto; - -import jakarta.validation.constraints.NotNull; -import lombok.Getter; - -import java.math.BigDecimal; - -@Getter -public class PaymentVerifyRequest { - @NotNull(message = "imp_uid는 필수입니다.") - String impUid; - @NotNull(message = "merchant_uid는 필수입니다.") - String merchantUid; - @NotNull(message = "amount는 필수입니다.") - BigDecimal amount; -} diff --git a/src/main/java/com/otakumap/domain/payment/entity/UserPayment.java b/src/main/java/com/otakumap/domain/payment/entity/UserPayment.java deleted file mode 100644 index cd4c1095..00000000 --- a/src/main/java/com/otakumap/domain/payment/entity/UserPayment.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.otakumap.domain.payment.entity; - -import com.otakumap.domain.payment.enums.PaymentStatus; -import com.otakumap.domain.point.entity.Point; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Entity -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class UserPayment extends BaseEntity{ - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column(name = "imp_uid", nullable = false) - private String impUid; - - @Column(name = "merchant_uid", nullable = false) - private String merchantUid; - - @Column(nullable = false) - private Long amount; - - @Column(name = "verified_at") - private LocalDateTime verifiedAt; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PaymentStatus status = PaymentStatus.PENDING; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "point_id", nullable = false) - private Point point; - - public void setPoint(Point point) { - this.point = point; - } -} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java b/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java deleted file mode 100644 index 5a398f75..00000000 --- a/src/main/java/com/otakumap/domain/payment/repository/PaymentRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.otakumap.domain.payment.repository; - -import com.otakumap.domain.payment.entity.UserPayment; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface PaymentRepository extends JpaRepository { - boolean existsByMerchantUid(String merchantUid); - Optional findByMerchantUid(String merchantUid); -} diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java index fc729bd4..5e4fdfba 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java @@ -1,13 +1,14 @@ package com.otakumap.domain.payment.service; -import com.otakumap.domain.payment.dto.PaymentVerifyRequest; +import com.otakumap.domain.order.dto.OrderDto; import com.otakumap.domain.user.entity.User; -import com.siot.IamportRestClient.exception.IamportResponseException; +import com.siot.IamportRestClient.response.IamportResponse; +import com.siot.IamportRestClient.response.Payment; import org.springframework.stereotype.Service; -import java.io.IOException; - @Service public interface PaymentCommandService { - void verifyPayment(User user, PaymentVerifyRequest request) throws IamportResponseException, IOException; + IamportResponse validateIamport(String imp_uid); + IamportResponse cancelPayment(String imp_uid); + String saveOrder(OrderDto orderDto, User user); } diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java index 57243db5..cbbf9161 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -1,90 +1,104 @@ package com.otakumap.domain.payment.service; -import com.otakumap.domain.payment.dto.PaymentVerifyRequest; -import com.otakumap.domain.payment.entity.UserPayment; -import com.otakumap.domain.payment.enums.PaymentStatus; -import com.otakumap.domain.payment.repository.PaymentRepository; +import com.otakumap.domain.event_review.repository.EventReviewRepository; +import com.otakumap.domain.order.dto.OrderDto; +import com.otakumap.domain.order.repository.OrderRepository; +import com.otakumap.domain.place_review.repository.PlaceReviewRepository; +import com.otakumap.domain.point.converter.PointConverter; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.point.repository.PointRepository; +import com.otakumap.domain.transaction.repository.TransactionRepository; import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.PaymentHandler; import com.siot.IamportRestClient.IamportClient; -import com.siot.IamportRestClient.exception.IamportResponseException; +import com.siot.IamportRestClient.request.CancelData; import com.siot.IamportRestClient.response.IamportResponse; import com.siot.IamportRestClient.response.Payment; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; import java.time.LocalDateTime; +import java.util.List; @Service @RequiredArgsConstructor @Transactional +@Slf4j public class PaymentCommandServiceImpl implements PaymentCommandService { private final IamportClient iamportClient; + private final OrderRepository orderRepository; private final PointRepository pointRepository; - private final PaymentRepository paymentRepository; - @Transactional - public void verifyPayment(User user, PaymentVerifyRequest request) throws IOException, IamportResponseException { - // 아임포트 결제 정보 조회 - IamportResponse paymentResponse = iamportClient.paymentByImpUid(request.getImpUid()); - Payment payment = paymentResponse.getResponse(); - - System.out.println("✅ Payment 정보: " + payment.getImpUid() + " " + payment.getMerchantUid() + " " + payment.getAmount()); - - if (payment == null) { - throw new PaymentHandler(ErrorStatus.PAYMENT_NOT_FOUND); - } - - // 결제 상태 확인 - if (!"paid".equals(payment.getStatus())) { - throw new PaymentHandler(ErrorStatus.PAYMENT_STATUS_INVALID); + /** + * 아임포트 서버로부터 결제 정보를 검증 + * @param imp_uid + */ + public IamportResponse validateIamport(String imp_uid) { + try { + IamportResponse payment = iamportClient.paymentByImpUid(imp_uid); + log.info("결제 요청 응답. 결제 내역 - 주문 번호: {}", payment.getResponse()); + return payment; + } catch (Exception e) { + log.info(e.getMessage()); + return null; } + } - // 결제 금액 검증 - if (!payment.getAmount().equals(request.getAmount())) { - throw new PaymentHandler(ErrorStatus.PAYMENT_AMOUNT_MISMATCH); + /** + * 아임포트 서버로부터 결제 취소 요청 + * + * @param imp_uid + * @return + */ + public IamportResponse cancelPayment(String imp_uid) { + try { + CancelData cancelData = new CancelData(imp_uid, true); + IamportResponse payment = iamportClient.cancelPaymentByImpUid(cancelData); + return payment; + } catch (Exception e) { + log.info(e.getMessage()); + return null; } + } - // 중복 결제 방지 - if (paymentRepository.findByMerchantUid(request.getMerchantUid()).isPresent()) { - throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); + /** + * 주문 정보 저장 + * @param orderDto + * @return + */ + public String saveOrder(OrderDto orderDto, User user){ + try { + // 주문 정보 저장 + orderRepository.save(orderDto.toEntity()); + + // 사용자에 해당하는 모든 Point 객체 조회 + List existingPoints = pointRepository.findByUserId(user.getId()); + + // 기존 포인트가 없다면 새로 생성하여 저장 + if (existingPoints.isEmpty()) { + PointConverter.savePoint(orderDto, user); + } else { + // 여러 포인트 객체가 있을 때 합산 + Long totalPoint = existingPoints.stream() + .mapToLong(Point::getPoint) + .sum(); // 여러 포인트 합산 + + // 새로 받은 포인트 합산 + totalPoint += orderDto.getPrice(); + + // 첫 번째 Point 객체에 업데이트하거나, 새로 포인트 객체를 만들어서 저장 + Point updatedPoint = existingPoints.get(0); // 기존 첫 번째 Point 객체 선택 + updatedPoint.setPoint(totalPoint); // 합산된 포인트로 업데이트 + updatedPoint.setChargedAt(LocalDateTime.now()); + pointRepository.save(updatedPoint); + } + return "주문 정보가 성공적으로 저장되었습니다."; + } catch (Exception e) { + log.info(e.getMessage()); + cancelPayment(orderDto.getImpUid()); + return "주문 정보 저장에 실패했습니다."; } - - // 포인트 먼저 생성 후 저장 - Point point = new Point( - Long.valueOf(String.valueOf(payment.getAmount())), // 충전된 포인트 - LocalDateTime.now(), // 충전 시간 - PaymentStatus.PAID, // 상태 설정 - user, // 사용자 정보 - null // 🔥 UserPayment는 아직 생성되지 않았으므로 null로 설정 - ); - - point = pointRepository.save(point); - - // 포인트를 포함한 UserPayment 생성 및 저장 - UserPayment userPayment = UserPayment.builder() - .user(user) - .impUid(payment.getImpUid()) - .merchantUid(payment.getMerchantUid()) - .amount(payment.getAmount().longValue()) - .verifiedAt(LocalDateTime.now()) - .status(PaymentStatus.PAID) - .point(point) - .build(); - - userPayment = paymentRepository.save(userPayment); - - // Point에 UserPayment 설정 후 다시 저장 - point.setUserPayment(userPayment); - pointRepository.save(point); - - System.out.println("✅ UserPayment 정보: " + userPayment); - System.out.println("✅ 생성된 Point 정보: " + point); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 3e837ae5..d48c70a0 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -1,16 +1,17 @@ package com.otakumap.domain.point.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; +import com.otakumap.domain.order.dto.OrderDto; +import com.otakumap.domain.payment.service.PaymentCommandService; import com.otakumap.domain.point.converter.PointConverter; -import com.otakumap.domain.point.dto.PointRequestDTO; import com.otakumap.domain.point.dto.PointResponseDTO; -import com.otakumap.domain.point.service.PointCommandService; import com.otakumap.domain.point.service.PointQueryservice; import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -18,18 +19,19 @@ @RequestMapping("/api/points") @RequiredArgsConstructor @Validated +@Slf4j public class PointController { - private final PointCommandService pointCommandService; + private final PaymentCommandService paymentCommandService; private final PointQueryservice pointQueryservice; @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") - public ApiResponse chargePoints( - @RequestBody @Valid PointRequestDTO.PointChargeDTO request, - @CurrentUser User user - ) { - pointCommandService.chargePoints(user, request.getPoint(), request.getMerchantUid()); - return ApiResponse.onSuccess("충전 성공하였습니다."); + public ResponseEntity processOrder(@RequestBody OrderDto orderDto, + @CurrentUser User user) { + // 구매한 후기 정보를 로그에 출력 + log.info("Received orders: {}", orderDto.toString()); + // 성공적으로 받아들였다는 응답 반환 + return ResponseEntity.ok(paymentCommandService.saveOrder(orderDto, user)); } @Operation(summary = "포인트 충전 내역 확인", description = "포인트 충전 내역을 확인합니다. page는 1부터 시작합니다.") diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java index f71ea835..6b1d80d3 100644 --- a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -1,13 +1,12 @@ package com.otakumap.domain.point.converter; -import com.otakumap.domain.payment.entity.UserPayment; +import com.otakumap.domain.order.dto.OrderDto; import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -34,28 +33,18 @@ public static PointResponseDTO.PointPreViewListDTO pointPreViewListDTO(Page transactionList = new ArrayList<>(); - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_payment_id", nullable = true) - private UserPayment userPayment; - public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User user) { this.point = point; this.chargedAt = chargedAt; this.status = status; this.user = user; } - - public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User user, UserPayment userPayment) { - this.point = point; - this.chargedAt = chargedAt; - this.status = status; - this.user = user; - this.userPayment = userPayment; // userPayment 필드를 추가로 설정 - } - - public void setUserPayment(UserPayment userPayment) { - this.userPayment = userPayment; - } public void addPoint(Long point) { this.point += point; @@ -88,4 +71,12 @@ public Long subPoint(Long point) { public boolean isAffordable(Long point) { return this.point >= point; } + + public void setPoint(Long totalPoint) { + this.point = totalPoint; + } + + public void setChargedAt(LocalDateTime now) { + this.chargedAt = now; + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java index 4e172c5c..4446fa5c 100644 --- a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java +++ b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java @@ -5,9 +5,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; + +import java.util.List; public interface PointRepository extends JpaRepository { Page findAllByUser(User user, PageRequest pageRequest); Point findTopByUserOrderByCreatedAtDesc(User user); + List findByUserId(Long id); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java index a2a6d339..c0aac0d4 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java @@ -3,5 +3,4 @@ import com.otakumap.domain.user.entity.User; public interface PointCommandService { - void chargePoints(User user, Long point, String merchantUid); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java index ddb277ef..17443cb3 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandServiceImpl.java @@ -1,13 +1,5 @@ package com.otakumap.domain.point.service; -import com.otakumap.domain.payment.entity.UserPayment; -import com.otakumap.domain.payment.repository.PaymentRepository; -import com.otakumap.domain.point.converter.PointConverter; -import com.otakumap.domain.point.entity.Point; -import com.otakumap.domain.point.repository.PointRepository; -import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.PaymentHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,23 +8,4 @@ @RequiredArgsConstructor @Transactional public class PointCommandServiceImpl implements PointCommandService { - - private final PaymentRepository paymentRepository; - private final PointRepository pointRepository; - - @Override - public void chargePoints(User user, Long point, String merchantUid) { - - if (paymentRepository.existsByMerchantUid(merchantUid)) { - throw new PaymentHandler(ErrorStatus.PAYMENT_DUPLICATE); - } - - // UserPayment 조회 - UserPayment userPayment = paymentRepository.findByMerchantUid(merchantUid) - .orElseThrow(() -> new PaymentHandler(ErrorStatus.PAYMENT_NOT_FOUND)); - - // 포인트 충전 내역 저장 - Point pointRecord = PointConverter.savePoint(user, point, userPayment); - pointRepository.save(pointRecord); - } } diff --git a/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java b/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java index 128741ed..e849e992 100644 --- a/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java +++ b/src/main/java/com/otakumap/domain/transaction/dto/TransactionResponseDTO.java @@ -1,5 +1,10 @@ package com.otakumap.domain.transaction.dto; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.transaction.entity.Transaction; +import com.otakumap.domain.transaction.enums.TransactionType; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -30,4 +35,35 @@ public static class TransactionListDTO { private Integer totalElements; private Boolean isLast; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class TransactionDetailDTO { + private Long id; + private Long pointId; + private TransactionType type; + private int amount; + private String impUid; + private String merchantUid; + private Long eventReviewId; + private Long placeReviewId; + public Transaction toEntity(Point point, EventReview eventReview, PlaceReview placeReview) { + Transaction.TransactionBuilder builder = Transaction.builder() + .point(point) + .type(type) + .amount(amount) + .impUid(impUid) + .merchantUid(merchantUid); + + if (eventReview != null) { + builder.eventReview(eventReview); + } else { + builder.placeReview(placeReview); + } + + return builder.build(); + } + } } diff --git a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java index 88dad15e..39d20d59 100644 --- a/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java +++ b/src/main/java/com/otakumap/domain/transaction/entity/Transaction.java @@ -34,6 +34,12 @@ public class Transaction extends BaseEntity { @Column(name = "amount", nullable = false) private int amount; + @Column(name = "imp_uid") + private String impUid; + + @Column(name = "merchant_uid") + private String merchantUid; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_review_id", referencedColumnName = "id") private EventReview eventReview; From ee6f172b64efe8bf4454cf5eb8b0b4cab8afd0f0 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 04:44:56 +0900 Subject: [PATCH 488/516] =?UTF-8?q?Refactor:=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/point/controller/PointController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index d48c70a0..5286aa64 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -26,12 +26,11 @@ public class PointController { @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") - public ResponseEntity processOrder(@RequestBody OrderDto orderDto, - @CurrentUser User user) { + public ApiResponse processOrder(@RequestBody OrderDto orderDto, @CurrentUser User user) { // 구매한 후기 정보를 로그에 출력 log.info("Received orders: {}", orderDto.toString()); // 성공적으로 받아들였다는 응답 반환 - return ResponseEntity.ok(paymentCommandService.saveOrder(orderDto, user)); + return ApiResponse.onSuccess(paymentCommandService.saveOrder(orderDto, user) ); } @Operation(summary = "포인트 충전 내역 확인", description = "포인트 충전 내역을 확인합니다. page는 1부터 시작합니다.") From 9582893667744829d35db0d6cdbe7b6a3afb8049 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 04:52:11 +0900 Subject: [PATCH 489/516] =?UTF-8?q?Feature:=20order=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=EC=97=90=20BaseEntity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/otakumap/domain/order/entity/Order.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/order/entity/Order.java b/src/main/java/com/otakumap/domain/order/entity/Order.java index 1b5297da..4a30ea89 100644 --- a/src/main/java/com/otakumap/domain/order/entity/Order.java +++ b/src/main/java/com/otakumap/domain/order/entity/Order.java @@ -1,5 +1,6 @@ package com.otakumap.domain.order.entity; +import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -12,7 +13,7 @@ @AllArgsConstructor @NoArgsConstructor @Table(name = "orders") -public class Order { +public class Order extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; From 982b1dcd97cf652e1a5992062d69867e772d33ba Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 20 Feb 2025 05:17:27 +0900 Subject: [PATCH 490/516] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8A=B8=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=95=8C=EB=A6=BC=20=EB=B9=88=EB=8F=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/service/RouteLikeCommandServiceImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 9f1f1cde..1e78fe2d 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -55,8 +55,7 @@ public void saveRouteLike(User user, Long routeId) { // 작성자에게 알림 전송 int likeCount = routeLikeRepository.countByRoute(route); - if (likeCount <= 10 || - (likeCount <= 50 && likeCount % 10 == 0) || + if ((likeCount >= 10 && likeCount <= 50 && likeCount % 10 == 0) || (likeCount <= 100 && likeCount % 50 == 0) || (likeCount % 100 == 0)) { From 8b147cc749d7f521b2b3734288f83d5c41cf467f Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 05:26:34 +0900 Subject: [PATCH 491/516] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=EC=99=80=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=201:N=20=EA=B4=80=EA=B3=84=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=9B=84=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=A0=20=EB=95=8C=20=EC=9E=91=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20true=EB=90=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 10 ++++++++++ .../place_review/entity/PlaceReview.java | 13 ++++++++---- .../service/ReviewCommandServiceImpl.java | 2 ++ .../otakumap/domain/route/entity/Route.java | 20 +++++++++++++++++++ .../route_like/dto/RouteLikeRequestDTO.java | 2 ++ .../service/RouteLikeCommandServiceImpl.java | 11 ++++++++++ 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index b0377624..5f30a727 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -40,6 +40,9 @@ public class EventReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long price; + @Column(nullable = false, columnDefinition = "boolean default false") + private boolean isWritten = false; // 후기 작성 여부를 추적하는 boolean 필드 + @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventReview") private List images = new ArrayList<>(); @@ -65,7 +68,14 @@ public class EventReview extends BaseEntity { @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL) private List transactionList = new ArrayList<>(); + @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL, orphanRemoval = true) + private List routes = new ArrayList<>(); + public void setPlaceList(List placeList) { this.placeList = placeList; } public void setAnimation(Animation animation) { this.animation = animation; } + + public void setIsWritten(boolean b) { + this.isWritten = b; + } } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index f904783c..ebf676d0 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -39,6 +39,9 @@ public class PlaceReview extends BaseEntity { @Column(columnDefinition = "bigint default 0 not null") private Long price; + @Column(nullable = false, columnDefinition = "boolean default false") + private boolean isWritten = false; // 후기 작성 여부를 추적하는 boolean 필드 + @OneToMany(cascade = CascadeType.ALL, mappedBy = "placeReview", orphanRemoval = true) private List images = new ArrayList<>(); @@ -53,15 +56,17 @@ public class PlaceReview extends BaseEntity { @JoinColumn(name = "animation_id") private Animation animation; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "route_id", referencedColumnName = "id") - private Route route; + @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL, orphanRemoval = true) + private List routes = new ArrayList<>(); @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL) private List transactionList = new ArrayList<>(); - public void setPlaceList(List placeList) { this.placeList = placeList; } public void setAnimation(Animation animation) { this.animation = animation; } + + public void setIsWritten(boolean b) { + this.isWritten = b; + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java index 09d07990..b1a49df1 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewCommandServiceImpl.java @@ -103,6 +103,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO // 먼저 PlaceReview를 저장 PlaceReview placeReview = ReviewConverter.toPlaceReview(request, user, route); placeReview.setAnimation(animation); + placeReview.setIsWritten(true); placeReview = placeReviewRepository.save(placeReview); // placeReviewPlace를 저장(placeReview에 해당하는 routeItem의 place 저장) @@ -121,6 +122,7 @@ private ReviewResponseDTO.CreatedReviewDTO saveReview(ReviewRequestDTO.CreateDTO // 먼저 EventReview를 저장 EventReview eventReview = ReviewConverter.toEventReview(request, user, route); eventReview.setAnimation(animation); + eventReview.setIsWritten(true); eventReview = eventReviewRepository.save(eventReview); // eventReviewPlaces 저장(eventReview에 해당하는 routeItem의 place 저장) diff --git a/src/main/java/com/otakumap/domain/route/entity/Route.java b/src/main/java/com/otakumap/domain/route/entity/Route.java index 0f0e3ca8..0c7d6c3c 100644 --- a/src/main/java/com/otakumap/domain/route/entity/Route.java +++ b/src/main/java/com/otakumap/domain/route/entity/Route.java @@ -1,5 +1,7 @@ package com.otakumap.domain.route.entity; +import com.otakumap.domain.event_review.entity.EventReview; +import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.route_item.entity.RouteItem; import com.otakumap.domain.route_like.entity.RouteLike; import com.otakumap.global.common.BaseEntity; @@ -28,6 +30,14 @@ public class Route extends BaseEntity { @OneToMany(mappedBy = "route", cascade = CascadeType.ALL, orphanRemoval = true) private List routeItems = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_review_id") + private PlaceReview placeReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_review_id") + private EventReview eventReview; + public void setName(String name) { this.name = name; } @@ -38,4 +48,14 @@ public void setRouteItems(List routeItems) { } this.routeItems.addAll(routeItems); } + + public void setPlaceReview(PlaceReview placeReview) { + this.placeReview = placeReview; + this.eventReview = null; // 이벤트 리뷰와 동시에 연결되지 않도록 + } + + public void setEventReview(EventReview eventReview) { + this.eventReview = eventReview; + this.placeReview = null; // 장소 리뷰와 동시에 연결되지 않도록 + } } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java index 75d0d467..4e397370 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java @@ -18,6 +18,8 @@ public static class FavoriteDTO { @Getter public static class SaveCustomRouteLikeDTO { + @NotBlank(message = "기존 루트 이름 입력은 필수입니다.") + private Long originalRouteId; @NotBlank(message = "루트 이름 입력은 필수입니다.") String name; @NotEmpty(message = "루트 아이템 입력은 필수입니다.") diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 9f1f1cde..7e74375d 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -93,8 +93,19 @@ public RouteLike saveCustomRouteLike(RouteLikeRequestDTO.SaveCustomRouteLikeDTO return RouteItemConverter.toRouteItem(routeItem.getItemOrder(), place); }).toList(); + // 기존 루트 정보 가져오기 + Route existingRoute = routeRepository.findById(request.getOriginalRouteId()) + .orElseThrow(() -> new RouteHandler(ErrorStatus.ROUTE_NOT_FOUND)); + Route route = RouteConverter.toRoute(request.getName(), routeItems); + // 기존 루트의 리뷰 타입을 확인하여 새로운 루트에 연결 + if (existingRoute.getPlaceReview() != null) { + route.setPlaceReview(existingRoute.getPlaceReview()); // 기존 PlaceReview 연결 + } else if (existingRoute.getEventReview() != null) { + route.setEventReview(existingRoute.getEventReview()); // 기존 EventReview 연결 + } + routeRepository.save(route); return routeLikeRepository.save(RouteLikeConverter.toRouteLike(user, route)); } From 3180139844b5eaccb92402edd1891fc27352c0b6 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 05:29:06 +0900 Subject: [PATCH 492/516] =?UTF-8?q?Chore:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/service/RouteLikeCommandServiceImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 7e74375d..f9effc66 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -1,11 +1,9 @@ package com.otakumap.domain.route_like.service; -import com.otakumap.domain.event_review.entity.EventReview; import com.otakumap.domain.event_review.repository.EventReviewRepository; import com.otakumap.domain.notification.service.NotificationCommandService; import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place.repository.PlaceRepository; -import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.place_review.repository.PlaceReviewRepository; import com.otakumap.domain.route.converter.RouteConverter; import com.otakumap.domain.route.entity.Route; From ed9fb8c2f96699d95e0a71e3371b8818cea71515 Mon Sep 17 00:00:00 2001 From: haerxeong Date: Thu, 20 Feb 2025 14:39:25 +0900 Subject: [PATCH 493/516] =?UTF-8?q?Fix:=20=EC=95=8C=EB=A6=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD=20API=EA=B0=80=20=EC=A0=95?= =?UTF-8?q?=EC=83=81=20=EC=9E=91=EB=8F=99=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/otakumap/domain/user/dto/UserRequestDTO.java | 2 +- src/main/java/com/otakumap/domain/user/entity/User.java | 4 ++-- .../otakumap/domain/user/service/UserCommandServiceImpl.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java index 962449d2..2b681ac7 100644 --- a/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java +++ b/src/main/java/com/otakumap/domain/user/dto/UserRequestDTO.java @@ -31,7 +31,7 @@ public static class NotificationSettingsRequestDTO { private Integer notificationType; @NotNull - private boolean isEnabled; + private Boolean isEnabled; } @Getter diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 2b4447e7..757e990a 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -54,11 +54,11 @@ public class User extends BaseEntity { @Column(nullable = false) private Integer donation; - @ColumnDefault("true") + @ColumnDefault("1") @Column(nullable = false) private Boolean isCommunityActivityNotified; - @ColumnDefault("true") + @ColumnDefault("1") @Column(nullable = false) private Boolean isEventBenefitsNotified; diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index c9ab5508..87b36f77 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -58,7 +58,7 @@ public void updateNotificationSettings(User user, UserRequestDTO.NotificationSet if (request.getNotificationType() != 1 && request.getNotificationType() != 2) { throw new UserHandler(ErrorStatus.INVALID_NOTIFICATION_TYPE); } - user.setNotification(request.getNotificationType(), request.isEnabled()); + user.setNotification(request.getNotificationType(), request.getIsEnabled()); userRepository.save(user); } From 94f96150b751b32c2246c0ef8dad039016ba422c Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 20 Feb 2025 15:05:30 +0900 Subject: [PATCH 494/516] =?UTF-8?q?Refactor:=20=EA=B5=AC=EB=A7=A4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EB=8F=99=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=98=84=EC=9E=AC=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20API=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/point/repository/PointRepository.java | 2 +- .../otakumap/domain/point/service/PointQueryserviceImpl.java | 2 +- .../domain/reviews/repository/ReviewRepositoryImpl.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java index 4446fa5c..7a0b0b7b 100644 --- a/src/main/java/com/otakumap/domain/point/repository/PointRepository.java +++ b/src/main/java/com/otakumap/domain/point/repository/PointRepository.java @@ -10,6 +10,6 @@ public interface PointRepository extends JpaRepository { Page findAllByUser(User user, PageRequest pageRequest); - Point findTopByUserOrderByCreatedAtDesc(User user); + Point findTopByUserOrderByChargedAtDesc(User user); List findByUserId(Long id); } diff --git a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java index 2094fba0..d22e9c9e 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java +++ b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java @@ -22,6 +22,6 @@ public Page getChargePointList(User user, Integer page) { @Override public PointResponseDTO.CurrentPointDTO getCurrentPoint(User user) { - return PointConverter.toCurrentPointDTO(pointRepository.findTopByUserOrderByCreatedAtDesc(user)); + return PointConverter.toCurrentPointDTO(pointRepository.findTopByUserOrderByChargedAtDesc(user)); } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index 268cd39b..c7e0eaa7 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -167,7 +167,7 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { @Override public ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type) { - Point buyerPoint = pointRepository.findTopByUserOrderByCreatedAtDesc(user); + Point buyerPoint = pointRepository.findTopByUserOrderByChargedAtDesc(user); // 이벤트 리뷰인 경우 if(type == ReviewType.EVENT) { EventReview review = eventReviewRepository.findById(reviewId) @@ -205,7 +205,7 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( throw new TransactionHandler(ErrorStatus.PURCHASE_SELF_CONTENT); } - Point sellerPoint = pointRepository.findTopByUserOrderByCreatedAtDesc(seller); + Point sellerPoint = pointRepository.findTopByUserOrderByChargedAtDesc(seller); if (sellerPoint == null) { sellerPoint = new Point(0L, LocalDateTime.now(), PaymentStatus.PAID, seller); } From 97575ee2d423aeac53402ed6567def76d4666d13 Mon Sep 17 00:00:00 2001 From: mk-star Date: Thu, 20 Feb 2025 15:14:10 +0900 Subject: [PATCH 495/516] =?UTF-8?q?Feat:=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EC=9D=80=20placeId=EB=A1=9C=20=EB=B3=80=EA=B2=BD=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/service/RouteLikeCommandServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java index 1e78fe2d..4631682e 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeCommandServiceImpl.java @@ -113,7 +113,7 @@ public RouteLike updateRouteLike(RouteLikeRequestDTO.UpdateRouteLikeDTO request, routeItem.setItemOrder(requestItem.getItemOrder()); } - Place place = placeRepository.findById(routeItem.getPlace().getId()).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); + Place place = placeRepository.findById(requestItem.getPlaceId()).orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_NOT_FOUND)); RouteItem updatedRouteItem = RouteItemConverter.toRouteItem(routeItem.getItemOrder(), place); updatedRouteItem.setRoute(route); From 66c7907194e1fc2857b27750fb84b93c7f5ebeda Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 15:32:31 +0900 Subject: [PATCH 496/516] =?UTF-8?q?Feat:=20=ED=8A=B9=EC=A0=95=20=EC=97=AC?= =?UTF-8?q?=ED=96=89=20=ED=9B=84=EA=B8=B0=20=EC=A1=B0=ED=9A=8C=20&=20?= =?UTF-8?q?=EA=B7=B8=EB=83=A5=20=EB=A3=A8=ED=8A=B8=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20(=EC=9E=91=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=97=AC=EB=B6=80=EA=B0=80=20true=EC=9D=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B2=8C=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/entity/EventReview.java | 6 +--- .../repository/EventReviewRepository.java | 10 ++++--- .../place_review/entity/PlaceReview.java | 3 ++ .../repository/PlaceReviewRepository.java | 7 +++-- .../reviews/converter/ReviewConverter.java | 18 +++++++---- .../service/ReviewQueryServiceImpl.java | 30 ++++++++++++------- .../apiPayload/code/status/ErrorStatus.java | 2 ++ 7 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java index 5f30a727..da18aa7b 100644 --- a/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java +++ b/src/main/java/com/otakumap/domain/event_review/entity/EventReview.java @@ -61,14 +61,10 @@ public class EventReview extends BaseEntity { @JoinColumn(name = "animation_id") private Animation animation; - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) - @JoinColumn(name = "route_id", referencedColumnName = "id") - private Route route; - @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL) private List transactionList = new ArrayList<>(); - @OneToMany(mappedBy = "placeReview", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "eventReview", cascade = CascadeType.ALL, orphanRemoval = true) private List routes = new ArrayList<>(); public void setPlaceList(List placeList) { this.placeList = placeList; } diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 008f1759..42bf619d 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -9,14 +9,16 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface EventReviewRepository extends JpaRepository { Page findAllByEvent(Event event, Pageable pageRequest); - Optional findByRouteId(Long routeId); - @Query("SELECT er.user FROM EventReview er WHERE er.route.id = :routeId") + @Query("SELECT er FROM EventReview er JOIN er.routes r WHERE r.id = :routeId") + Optional findByRouteId(@Param("routeId") Long routeId); + @Query("SELECT r.eventReview.user FROM Route r WHERE r.id = :routeId") Optional findUserByRouteId(@Param("routeId") Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); -} - + List findByIdAndIsWrittenTrue(Long reviewId); +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index ebf676d0..034d348a 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -6,6 +6,8 @@ import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.user.entity.User; +import com.otakumap.global.apiPayload.code.status.ErrorStatus; +import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -69,4 +71,5 @@ public class PlaceReview extends BaseEntity { public void setIsWritten(boolean b) { this.isWritten = b; } + } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 25155da5..c8bd85e6 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -8,15 +8,18 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface PlaceReviewRepository extends JpaRepository, PlaceReviewRepositoryCustom { Page findAllByUserId(Long userId, PageRequest pageRequest); void deleteAllByUserId(Long userId); - Optional findByRouteId(Long routeId); - @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.route.id = :routeId") + @Query("SELECT pr FROM PlaceReview pr JOIN pr.routes r WHERE r.id = :routeId") + Optional findByRouteId(@Param("routeId") Long routeId); + @Query("SELECT r.placeReview.user FROM Route r WHERE r.id = :routeId") Optional findUserByRouteId(@Param("routeId") Long routeId); @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); + List findByIdAndIsWrittenTrue(Long reviewId); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index f6c3c40d..4277a4ed 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -102,7 +102,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi .userName(placeReview.getUser().getName()) .profileImage(ImageConverter.toImageDTO(placeReview.getUser().getProfileImage())) .createdAt(placeReview.getCreatedAt()) - .route(RouteConverter.toRouteDTO(placeReview.getRoute())) + .route(placeReview.getRoutes().isEmpty() ? null : RouteConverter.toRouteDTO(placeReview.getRoutes().get(0))) .build(); } @@ -121,7 +121,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .userName(eventReview.getUser().getName()) .profileImage(ImageConverter.toImageDTO(eventReview.getUser().getProfileImage())) .createdAt(eventReview.getCreatedAt()) - .route(RouteConverter.toRouteDTO(eventReview.getRoute())) + .route(eventReview.getRoutes().isEmpty() ? null : RouteConverter.toRouteDTO(eventReview.getRoutes().get(0))) .build(); } @@ -134,23 +134,29 @@ public static ReviewResponseDTO.CreatedReviewDTO toCreatedReviewDTO(Long reviewI } public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User user, Route route) { - return EventReview.builder() + EventReview eventReview = EventReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) - .route(route) .build(); + + // route를 리스트에 추가 + eventReview.getRoutes().add(route); + return eventReview; } public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Route route) { - return PlaceReview.builder() + PlaceReview placeReview = PlaceReview.builder() .title(request.getTitle()) .content(request.getContent()) .view(0L) .user(user) - .route(route) .build(); + + // route를 리스트에 추가 + placeReview.getRoutes().add(route); + return placeReview; } public static List toPlaceReviewPlaceList(List places, PlaceReview placeReview) { diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index a6dbd114..dc78f674 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -17,6 +17,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -37,18 +39,26 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { @Override public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewType type) { - if(type == ReviewType.EVENT) { - EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); - - return ReviewConverter.toEventReviewDetailDTO(eventReview); + if (type == ReviewType.EVENT) { + List eventReviews = eventReviewRepository.findByIdAndIsWrittenTrue(reviewId); + if (eventReviews.isEmpty()) { + throw new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND); + } + if (eventReviews.size() > 1) { + throw new EventHandler(ErrorStatus.MULTIPLE_WRITTEN_EVENT_REVIEWS_FOUND); + } + return ReviewConverter.toEventReviewDetailDTO(eventReviews.get(0)); } else if (type == ReviewType.PLACE) { - PlaceReview placeReview = placeReviewRepository.findById(reviewId) - .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); - - return ReviewConverter.toPlaceReviewDetailDTO(placeReview); + List placeReviews = placeReviewRepository.findByIdAndIsWrittenTrue(reviewId); + if (placeReviews.isEmpty()) { + throw new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND); + } + if (placeReviews.size() > 1) { + throw new PlaceHandler(ErrorStatus.MULTIPLE_WRITTEN_PLACE_REVIEWS_FOUND); + } + return ReviewConverter.toPlaceReviewDetailDTO(placeReviews.get(0)); } else { throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java index f0daf9f2..0b90b5ba 100644 --- a/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/otakumap/global/apiPayload/code/status/ErrorStatus.java @@ -42,6 +42,7 @@ public enum ErrorStatus implements BaseErrorCode { // 명소 후기 관련 에러 PLACE_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "PLACE4005", "존재하지 않는 명소 후기입니다."), + MULTIPLE_WRITTEN_PLACE_REVIEWS_FOUND(HttpStatus.BAD_REQUEST, "PLACE4006", "고유한 명소 후기가 조회되지 않습니다."), // 이벤트 좋아요 관련 에러 EVENT_LIKE_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4001", "저장되지 않은 이벤트입니다."), @@ -52,6 +53,7 @@ public enum ErrorStatus implements BaseErrorCode { // 이벤트 후기 관련 에러 EVENT_REVIEW_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4003", "존재하지 않는 이벤트 후기입니다."), + MULTIPLE_WRITTEN_EVENT_REVIEWS_FOUND(HttpStatus.BAD_REQUEST, "EVENT4009", "고유한 이벤트 후기가 조회되지 않습니다."), // 이벤트 카테고리별 검색 관련 에러 EVENT_CONDITION_NOT_FOUND(HttpStatus.BAD_REQUEST, "EVENT4004", "이벤트 검색 조건이 존재하지 않습니다."), From 319550f47921fbf1e5fb5840c629fb9ce64ec6e7 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 15:33:53 +0900 Subject: [PATCH 497/516] =?UTF-8?q?Chore:=20unused=20import=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/place_review/entity/PlaceReview.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index 034d348a..bb7eb3ce 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -6,8 +6,6 @@ import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.transaction.entity.Transaction; import com.otakumap.domain.user.entity.User; -import com.otakumap.global.apiPayload.code.status.ErrorStatus; -import com.otakumap.global.apiPayload.exception.handler.ReviewHandler; import com.otakumap.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; From 622bb75f55cc0a1d66ccc2a14e884ef030c9f949 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 16:02:42 +0900 Subject: [PATCH 498/516] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8&?= =?UTF-8?q?=EB=AA=85=EC=86=8C=20=EB=A6=AC=EB=B7=B0=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(isWritten=EC=9D=B4=20True=EC=9D=B8=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EB=A7=8C=20=EC=A1=B0=ED=9A=8C=EB=90=98=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_review/repository/EventReviewRepository.java | 1 + .../service/EventReviewCommandServiceImpl.java | 7 +++++-- .../otakumap/domain/place_review/entity/PlaceReview.java | 3 +++ .../place_review/repository/PlaceReviewRepository.java | 5 +++++ .../place_review/service/PlaceReviewQueryServiceImpl.java | 1 + 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 42bf619d..f775b277 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -21,4 +21,5 @@ public interface EventReviewRepository extends JpaRepository @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); List findByIdAndIsWrittenTrue(Long reviewId); + Page findAllByEventAndIsWrittenTrue(Event event, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java index 7151a0ac..c3dec50a 100644 --- a/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/event_review/service/EventReviewCommandServiceImpl.java @@ -20,7 +20,10 @@ public class EventReviewCommandServiceImpl implements EventReviewCommandService @Override public Page getEventReviews(Long eventId, Integer page) { - Event event = eventRepository.findById(eventId).orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); - return eventReviewRepository.findAllByEvent(event, PageRequest.of(page, 4)); + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_NOT_FOUND)); + + // isWritten이 true인 EventReview만 조회 + return eventReviewRepository.findAllByEventAndIsWrittenTrue(event, PageRequest.of(page, 4)); } } diff --git a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java index bb7eb3ce..eb4c088c 100644 --- a/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java +++ b/src/main/java/com/otakumap/domain/place_review/entity/PlaceReview.java @@ -70,4 +70,7 @@ public void setIsWritten(boolean b) { this.isWritten = b; } + public boolean getIsWritten() { + return this.isWritten; + } } diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index c8bd85e6..1bdbd3e6 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -1,5 +1,6 @@ package com.otakumap.domain.place_review.repository; +import com.otakumap.domain.place.entity.Place; import com.otakumap.domain.place_review.entity.PlaceReview; import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; @@ -22,4 +23,8 @@ public interface PlaceReviewRepository extends JpaRepository, @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); List findByIdAndIsWrittenTrue(Long reviewId); + @Query("SELECT pr FROM PlaceReview pr " + + "JOIN PlaceReviewPlace prp ON prp.placeReview = pr " + + "WHERE prp.place = :place AND pr.isWritten = true") + List findByPlaceAndIsWrittenTrue(@Param("place") Place place); } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java index 957b48ed..6de03647 100644 --- a/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/place_review/service/PlaceReviewQueryServiceImpl.java @@ -51,6 +51,7 @@ public PlaceReviewResponseDTO.PlaceAnimationReviewDTO getReviewsByPlace(Long pla List allReviews = placeReviewPlaces.stream() .map(prp -> placeReviewRepository.findById(prp.getPlaceReview().getId())) .flatMap(Optional::stream) + .filter(PlaceReview::getIsWritten) // isWritten이 true인 것만 필터링 .distinct() .toList(); From 4ac68197ba69230a03cfc5072d93a8709500bebc Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 20 Feb 2025 17:50:05 +0900 Subject: [PATCH 499/516] =?UTF-8?q?Refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=95=9C=20=EC=A4=84=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/EventShortReviewConverter.java | 9 +++++++++ .../dto/EventShortReviewResponseDTO.java | 19 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java b/src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java index 68fa54c4..43cb0006 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java +++ b/src/main/java/com/otakumap/domain/event_short_review/converter/EventShortReviewConverter.java @@ -35,6 +35,7 @@ public static EventShortReviewResponseDTO.NewEventShortReviewDTO toNewEventShort public static EventShortReviewResponseDTO.EventShortReviewDTO toEventShortReviewDTO(EventShortReview eventShortReview) { return EventShortReviewResponseDTO.EventShortReviewDTO.builder() .id(eventShortReview.getId()) + .user(EventShortReviewConverter.toEventShortReviewUserDTO(eventShortReview.getUser())) .content(eventShortReview.getContent()) .rating(eventShortReview.getRating()) .profileImage(ImageConverter.toImageDTO(eventShortReview.getUser().getProfileImage())) @@ -51,4 +52,12 @@ public static EventShortReviewResponseDTO.EventShortReviewListDTO toEventShortRe .totalPages(reviewList.getTotalPages()) .build(); } + + public static EventShortReviewResponseDTO.EventShortReviewUserDTO toEventShortReviewUserDTO(User user) { + return EventShortReviewResponseDTO.EventShortReviewUserDTO.builder() + .userId(user.getId()) + .nickname(user.getNickname()) + .profileImage(user.getProfileImage().getFileUrl()) + .build(); + } } diff --git a/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java index 53bedeb1..d93d8883 100644 --- a/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/event_short_review/dto/EventShortReviewResponseDTO.java @@ -40,9 +40,20 @@ public static class EventShortReviewListDTO { @NoArgsConstructor @AllArgsConstructor public static class EventShortReviewDTO { - Long id; - String content; - Float rating; - ImageResponseDTO.ImageDTO profileImage; + private Long id; + private EventShortReviewUserDTO user; + private String content; + private Float rating; + private ImageResponseDTO.ImageDTO profileImage; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class EventShortReviewUserDTO { + private Long userId; + private String nickname; + private String profileImage; } } From 64196b03645b91b14ac1932594b716ce7b3a1953 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 20:02:22 +0900 Subject: [PATCH 500/516] =?UTF-8?q?Refactor:=20NotNull=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20&=20findById=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EventReviewRepository.java | 2 -- .../repository/PlaceReviewRepository.java | 1 - .../service/ReviewQueryServiceImpl.java | 24 +++++-------------- .../route_like/dto/RouteLikeRequestDTO.java | 2 +- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index 751d92e1..d6a63ba7 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -9,7 +9,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; import java.util.Optional; public interface EventReviewRepository extends JpaRepository { @@ -20,7 +19,6 @@ public interface EventReviewRepository extends JpaRepository Optional findUserByRouteId(@Param("routeId") Long routeId); @Query("SELECT er.user FROM EventReview er WHERE er.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); - List findByIdAndIsWrittenTrue(Long reviewId); Page findAllByEventAndIsWrittenTrue(Event event, Pageable pageable); List findAllByUserId(Long userId); void deleteAllByUserId(Long userId); diff --git a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java index 465021c4..9c033f03 100644 --- a/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java +++ b/src/main/java/com/otakumap/domain/place_review/repository/PlaceReviewRepository.java @@ -22,7 +22,6 @@ public interface PlaceReviewRepository extends JpaRepository, Optional findUserByRouteId(@Param("routeId") Long routeId); @Query("SELECT pr.user FROM PlaceReview pr WHERE pr.id = :reviewId") User findUserById(@Param("reviewId") Long reviewId); - List findByIdAndIsWrittenTrue(Long reviewId); @Query("SELECT pr FROM PlaceReview pr " + "JOIN PlaceReviewPlace prp ON prp.placeReview = pr " + "WHERE prp.place = :place AND pr.isWritten = true") diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index dc78f674..66c76eee 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -17,8 +17,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -40,23 +38,13 @@ public ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews() { @Override public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewType type) { if (type == ReviewType.EVENT) { - List eventReviews = eventReviewRepository.findByIdAndIsWrittenTrue(reviewId); - if (eventReviews.isEmpty()) { - throw new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND); - } - if (eventReviews.size() > 1) { - throw new EventHandler(ErrorStatus.MULTIPLE_WRITTEN_EVENT_REVIEWS_FOUND); - } - return ReviewConverter.toEventReviewDetailDTO(eventReviews.get(0)); + EventReview eventReview = eventReviewRepository.findById(reviewId) + .orElseThrow(() -> new EventHandler(ErrorStatus.EVENT_REVIEW_NOT_FOUND)); + return ReviewConverter.toEventReviewDetailDTO(eventReview); } else if (type == ReviewType.PLACE) { - List placeReviews = placeReviewRepository.findByIdAndIsWrittenTrue(reviewId); - if (placeReviews.isEmpty()) { - throw new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND); - } - if (placeReviews.size() > 1) { - throw new PlaceHandler(ErrorStatus.MULTIPLE_WRITTEN_PLACE_REVIEWS_FOUND); - } - return ReviewConverter.toPlaceReviewDetailDTO(placeReviews.get(0)); + PlaceReview placeReview = placeReviewRepository.findById(reviewId) + .orElseThrow(() -> new PlaceHandler(ErrorStatus.PLACE_REVIEW_NOT_FOUND)); + return ReviewConverter.toPlaceReviewDetailDTO(placeReview); } else { throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java index 4e397370..d6505c50 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeRequestDTO.java @@ -18,7 +18,7 @@ public static class FavoriteDTO { @Getter public static class SaveCustomRouteLikeDTO { - @NotBlank(message = "기존 루트 이름 입력은 필수입니다.") + @NotNull(message = "기존 루트 이름 입력은 필수입니다.") private Long originalRouteId; @NotBlank(message = "루트 이름 입력은 필수입니다.") String name; From 56599f0347f2be0d5030c55e223e812ef5a7d756 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 20:07:19 +0900 Subject: [PATCH 501/516] =?UTF-8?q?Fix:=20=ED=95=84=EC=9A=94=ED=95=9C=20im?= =?UTF-8?q?port=EB=AC=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_review/repository/EventReviewRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java index d6a63ba7..a4e0c39b 100644 --- a/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java +++ b/src/main/java/com/otakumap/domain/event_review/repository/EventReviewRepository.java @@ -9,6 +9,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface EventReviewRepository extends JpaRepository { From 83d144182d5cdddd2bcae782a8241d911991dcea Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 20:08:31 +0900 Subject: [PATCH 502/516] =?UTF-8?q?Fix:=20getRoute()=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/event_review/converter/EventReviewConverter.java | 2 -- .../otakumap/domain/reviews/converter/ReviewConverter.java | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java index a793030a..95e8c275 100644 --- a/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java +++ b/src/main/java/com/otakumap/domain/event_review/converter/EventReviewConverter.java @@ -42,7 +42,5 @@ public static EventReviewResponseDTO.EventReviewPreViewListDTO eventReviewPreVie .isFirst(eventReviewList.isFirst()) .isLast(eventReviewList.isLast()) .build(); - - } } diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index 7cc54d25..e77f56a3 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -144,10 +144,11 @@ public static EventReview toEventReview(ReviewRequestDTO.CreateDTO request, User .build(); // route를 리스트에 추가 - eventReview.getRoutes().add(route); + route.setEventReview(eventReview); return eventReview; } + public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User user, Route route, Long price) { PlaceReview placeReview = PlaceReview.builder() .title(request.getTitle()) @@ -158,7 +159,7 @@ public static PlaceReview toPlaceReview(ReviewRequestDTO.CreateDTO request, User .build(); // route를 리스트에 추가 - placeReview.getRoutes().add(route); + route.setPlaceReview(placeReview); return placeReview; } From 2f7b537ae80f2feb18911f45d7033b9295847f4c Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 20 Feb 2025 20:24:11 +0900 Subject: [PATCH 503/516] =?UTF-8?q?Feat:=20=EA=B5=AC=EB=A7=A4=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reviews/controller/ReviewController.java | 12 ++++++++++++ .../domain/reviews/dto/ReviewResponseDTO.java | 8 ++++++++ .../reviews/repository/ReviewRepositoryCustom.java | 1 + .../reviews/repository/ReviewRepositoryImpl.java | 9 +++++++++ .../domain/reviews/service/ReviewQueryService.java | 2 ++ .../reviews/service/ReviewQueryServiceImpl.java | 10 +++++++++- 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java index 308523d9..ba3a6137 100644 --- a/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java +++ b/src/main/java/com/otakumap/domain/reviews/controller/ReviewController.java @@ -94,4 +94,16 @@ public ApiResponse purchaseReview(@CurrentU @RequestParam(defaultValue = "PLACE") ReviewType type) { return ApiResponse.onSuccess(reviewCommandService.purchaseReview(user, reviewId, type)); } + + @GetMapping(value = "/reviews/purchase") + @Operation(summary = "후기 구매 여부 확인", description = "후기를 구매했는지 확인합니다.") + @Parameters({ + @Parameter(name = "reviewId", description = "이벤트 or 명소의 후기 id 입니다."), + @Parameter(name = "type", description = "리뷰의 종류를 특정합니다. 'EVENT' 또는 'PLACE' 여야 합니다.") + }) + public ApiResponse checkIsPurchasedReview(@CurrentUser User user, + @RequestParam @ValidReviewId Long reviewId, + @RequestParam(defaultValue = "PLACE") ReviewType type) { + return ApiResponse.onSuccess(reviewQueryService.getIsPurchasedReview(user, reviewId, type)); + } } diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 7ca0fb23..1b77a1d2 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -84,4 +84,12 @@ public static class CreatedReviewDTO { public static class PurchaseReviewDTO { private Long remainingPoints; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class IsPurchasedReviewDTO { + private Boolean isPurchased; + } } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java index 936c3a8a..1e9303a4 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryCustom.java @@ -9,4 +9,5 @@ public interface ReviewRepositoryCustom { Page getReviewsByKeyword(String keyword, int page, int size, String sort); ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews(); ReviewResponseDTO.PurchaseReviewDTO purchaseReview(User user, Long reviewId, ReviewType type); + Boolean getIsPurchasedReview(User user, Long reviewId, ReviewType type); } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index c7e0eaa7..f941d596 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -232,4 +232,13 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( .remainingPoints(remainingPoints) .build(); } + + @Override + public Boolean getIsPurchasedReview(User user, Long reviewId, ReviewType type) { + if(type == ReviewType.EVENT) { + return transactionRepository.existsByPoint_UserAndEventReview(user, eventReviewRepository.getById(reviewId)); + } else { + return transactionRepository.existsByPoint_UserAndPlaceReview(user, placeReviewRepository.getById(reviewId)); + } + } } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java index 96d86d17..ba3f9aea 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryService.java @@ -2,6 +2,7 @@ import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; +import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; public interface ReviewQueryService { @@ -11,4 +12,5 @@ public interface ReviewQueryService { ReviewResponseDTO.Top7ReviewPreViewListDTO getTop7Reviews(); + ReviewResponseDTO.IsPurchasedReviewDTO getIsPurchasedReview(User user, Long reviewId, ReviewType type); } diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 66c76eee..22655e23 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -8,6 +8,7 @@ import com.otakumap.domain.reviews.dto.ReviewResponseDTO; import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.reviews.repository.ReviewRepositoryCustom; +import com.otakumap.domain.user.entity.User; import com.otakumap.global.apiPayload.code.status.ErrorStatus; import com.otakumap.global.apiPayload.exception.handler.EventHandler; import com.otakumap.global.apiPayload.exception.handler.PlaceHandler; @@ -49,4 +50,11 @@ public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewTy throw new ReviewHandler(ErrorStatus.INVALID_REVIEW_TYPE); } } -} \ No newline at end of file + + @Override + public ReviewResponseDTO.IsPurchasedReviewDTO getIsPurchasedReview(User user, Long reviewId, ReviewType type) { + return ReviewResponseDTO.IsPurchasedReviewDTO.builder() + .isPurchased(reviewRepositoryCustom.getIsPurchasedReview(user, reviewId, type)) + .build(); + } +} From 2e4c4a11b13fbd9f6b57d1e070ee4f03eed0f43d Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 20 Feb 2025 20:29:14 +0900 Subject: [PATCH 504/516] =?UTF-8?q?Refactor:=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20placeRevi?= =?UTF-8?q?ew=EB=8A=94=20price=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=97=90=EB=9F=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/reviews/converter/ReviewConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index e77f56a3..e8a64ea0 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -95,6 +95,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi .title(placeReview.getTitle()) .view(placeReview.getView()) .content(placeReview.getContent()) + .price(placeReview.getPrice()) .reviewImages(placeReview.getImages().stream() .filter(Objects::nonNull) .map(ImageConverter::toImageDTO) From beb08a2e164786c5046c5cb718144fd101d71269 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 20 Feb 2025 20:31:50 +0900 Subject: [PATCH 505/516] =?UTF-8?q?Refactor:=20converter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/reviews/converter/ReviewConverter.java | 6 ++++++ .../domain/reviews/service/ReviewQueryServiceImpl.java | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index e8a64ea0..eb0e55a1 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -198,4 +198,10 @@ public static PlaceAnimation toPlaceAnimation(Place place, Animation animation) .animation(animation) .build(); } + + public static ReviewResponseDTO.IsPurchasedReviewDTO toIsPurchasedReviewDTO(Boolean isPurchased) { + return ReviewResponseDTO.IsPurchasedReviewDTO.builder() + .isPurchased(isPurchased) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java index 22655e23..1d87baec 100644 --- a/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/service/ReviewQueryServiceImpl.java @@ -53,8 +53,6 @@ public ReviewResponseDTO.ReviewDetailDTO getReviewDetail(Long reviewId, ReviewTy @Override public ReviewResponseDTO.IsPurchasedReviewDTO getIsPurchasedReview(User user, Long reviewId, ReviewType type) { - return ReviewResponseDTO.IsPurchasedReviewDTO.builder() - .isPurchased(reviewRepositoryCustom.getIsPurchasedReview(user, reviewId, type)) - .build(); + return ReviewConverter.toIsPurchasedReviewDTO(reviewRepositoryCustom.getIsPurchasedReview(user, reviewId, type)); } } From d2faffa8b2d3b587a862b4c274a560182d8c1a99 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 21:28:34 +0900 Subject: [PATCH 506/516] =?UTF-8?q?Refactor:=20Order=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20Point=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=B9=BC=EB=9F=BC=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(Point=20=EC=B6=A9=EC=A0=84=20=EB=82=B4=EC=97=AD=EC=9D=80=20Poi?= =?UTF-8?q?nt=20=ED=85=8C=EC=9D=B4=EB=B8=94=EC=97=90=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/order/dto/OrderDto.java | 34 +++++------ .../otakumap/domain/order/entity/Order.java | 58 +++++++++---------- .../order/repository/OrderRepository.java | 12 ++-- .../service/PaymentCommandService.java | 4 +- .../service/PaymentCommandServiceImpl.java | 43 ++------------ .../point/controller/PointController.java | 8 +-- .../point/converter/PointConverter.java | 9 --- .../domain/point/dto/PointResponseDTO.java | 32 ++++++++++ .../otakumap/domain/point/entity/Point.java | 10 +++- .../com/otakumap/domain/user/entity/User.java | 17 ++++++ 10 files changed, 120 insertions(+), 107 deletions(-) diff --git a/src/main/java/com/otakumap/domain/order/dto/OrderDto.java b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java index f8240f6d..d12479c5 100644 --- a/src/main/java/com/otakumap/domain/order/dto/OrderDto.java +++ b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java @@ -1,19 +1,19 @@ package com.otakumap.domain.order.dto; -import com.otakumap.domain.order.entity.Order; -import lombok.Getter; - -@Getter -public class OrderDto { - Long price; - String impUid; - String merchantUid; - - public Order toEntity() { - return Order.builder() - .price(price) - .impUid(impUid) - .merchantUid(merchantUid) - .build(); - } -} \ No newline at end of file +//import com.otakumap.domain.order.entity.Order; +//import lombok.Getter; +// +//@Getter +//public class OrderDto { +// Long price; +// String impUid; +// String merchantUid; +// +// public Order toEntity() { +// return Order.builder() +// .price(price) +// .impUid(impUid) +// .merchantUid(merchantUid) +// .build(); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/entity/Order.java b/src/main/java/com/otakumap/domain/order/entity/Order.java index 4a30ea89..c4f74af4 100644 --- a/src/main/java/com/otakumap/domain/order/entity/Order.java +++ b/src/main/java/com/otakumap/domain/order/entity/Order.java @@ -1,29 +1,29 @@ -package com.otakumap.domain.order.entity; - -import com.otakumap.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Builder -@Getter -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "orders") -public class Order extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private Long price; - - @Column(nullable = false) - private String impUid; - - @Column(nullable = false) - private String merchantUid; -} \ No newline at end of file +//package com.otakumap.domain.order.entity; +// +//import com.otakumap.global.common.BaseEntity; +//import jakarta.persistence.*; +//import lombok.AllArgsConstructor; +//import lombok.Builder; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +// +//@Entity +//@Builder +//@Getter +//@AllArgsConstructor +//@NoArgsConstructor +//@Table(name = "orders") +//public class Order extends BaseEntity { +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long id; +// +// @Column(nullable = false) +// private Long price; +// +// @Column(nullable = false) +// private String impUid; +// +// @Column(nullable = false) +// private String merchantUid; +//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java index 52ae71dc..72dab8ac 100644 --- a/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java +++ b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java @@ -1,7 +1,7 @@ package com.otakumap.domain.order.repository; - -import com.otakumap.domain.order.entity.Order; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface OrderRepository extends JpaRepository { -} \ No newline at end of file +// +//import com.otakumap.domain.order.entity.Order; +//import org.springframework.data.jpa.repository.JpaRepository; +// +//public interface OrderRepository extends JpaRepository { +//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java index 5e4fdfba..87ae0374 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandService.java @@ -1,6 +1,6 @@ package com.otakumap.domain.payment.service; -import com.otakumap.domain.order.dto.OrderDto; +import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.user.entity.User; import com.siot.IamportRestClient.response.IamportResponse; import com.siot.IamportRestClient.response.Payment; @@ -10,5 +10,5 @@ public interface PaymentCommandService { IamportResponse validateIamport(String imp_uid); IamportResponse cancelPayment(String imp_uid); - String saveOrder(OrderDto orderDto, User user); + String savePoint(PointResponseDTO.PointSaveResponseDTO pointResponseDTO, User user); } diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java index cbbf9161..d4d603b3 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -1,13 +1,7 @@ package com.otakumap.domain.payment.service; -import com.otakumap.domain.event_review.repository.EventReviewRepository; -import com.otakumap.domain.order.dto.OrderDto; -import com.otakumap.domain.order.repository.OrderRepository; -import com.otakumap.domain.place_review.repository.PlaceReviewRepository; -import com.otakumap.domain.point.converter.PointConverter; -import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.repository.PointRepository; -import com.otakumap.domain.transaction.repository.TransactionRepository; import com.otakumap.domain.user.entity.User; import com.siot.IamportRestClient.IamportClient; import com.siot.IamportRestClient.request.CancelData; @@ -18,9 +12,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional @@ -28,7 +19,6 @@ public class PaymentCommandServiceImpl implements PaymentCommandService { private final IamportClient iamportClient; - private final OrderRepository orderRepository; private final PointRepository pointRepository; /** @@ -65,39 +55,16 @@ public IamportResponse cancelPayment(String imp_uid) { /** * 주문 정보 저장 - * @param orderDto + * @param pointResponseDTO * @return */ - public String saveOrder(OrderDto orderDto, User user){ + public String savePoint(PointResponseDTO.PointSaveResponseDTO pointResponseDTO, User user){ try { - // 주문 정보 저장 - orderRepository.save(orderDto.toEntity()); - - // 사용자에 해당하는 모든 Point 객체 조회 - List existingPoints = pointRepository.findByUserId(user.getId()); - - // 기존 포인트가 없다면 새로 생성하여 저장 - if (existingPoints.isEmpty()) { - PointConverter.savePoint(orderDto, user); - } else { - // 여러 포인트 객체가 있을 때 합산 - Long totalPoint = existingPoints.stream() - .mapToLong(Point::getPoint) - .sum(); // 여러 포인트 합산 - - // 새로 받은 포인트 합산 - totalPoint += orderDto.getPrice(); - - // 첫 번째 Point 객체에 업데이트하거나, 새로 포인트 객체를 만들어서 저장 - Point updatedPoint = existingPoints.get(0); // 기존 첫 번째 Point 객체 선택 - updatedPoint.setPoint(totalPoint); // 합산된 포인트로 업데이트 - updatedPoint.setChargedAt(LocalDateTime.now()); - pointRepository.save(updatedPoint); - } + pointRepository.save(pointResponseDTO.toEntity(user)); return "주문 정보가 성공적으로 저장되었습니다."; } catch (Exception e) { log.info(e.getMessage()); - cancelPayment(orderDto.getImpUid()); + cancelPayment(pointResponseDTO.getImpUid()); return "주문 정보 저장에 실패했습니다."; } } diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index 5286aa64..d1d9118a 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -1,7 +1,6 @@ package com.otakumap.domain.point.controller; import com.otakumap.domain.auth.jwt.annotation.CurrentUser; -import com.otakumap.domain.order.dto.OrderDto; import com.otakumap.domain.payment.service.PaymentCommandService; import com.otakumap.domain.point.converter.PointConverter; import com.otakumap.domain.point.dto.PointResponseDTO; @@ -11,7 +10,6 @@ import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -26,11 +24,11 @@ public class PointController { @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") - public ApiResponse processOrder(@RequestBody OrderDto orderDto, @CurrentUser User user) { + public ApiResponse processOrder(@RequestBody PointResponseDTO.PointSaveResponseDTO pointResponseDTO, @CurrentUser User user) { // 구매한 후기 정보를 로그에 출력 - log.info("Received orders: {}", orderDto.toString()); + //log.info("Received orders: {}", pointResponseDTO.toString()); // 성공적으로 받아들였다는 응답 반환 - return ApiResponse.onSuccess(paymentCommandService.saveOrder(orderDto, user) ); + return ApiResponse.onSuccess(paymentCommandService.savePoint(pointResponseDTO, user)); } @Operation(summary = "포인트 충전 내역 확인", description = "포인트 충전 내역을 확인합니다. page는 1부터 시작합니다.") diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java index 6b1d80d3..120bed2a 100644 --- a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -1,6 +1,5 @@ package com.otakumap.domain.point.converter; -import com.otakumap.domain.order.dto.OrderDto; import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; @@ -39,12 +38,4 @@ public static PointResponseDTO.CurrentPointDTO toCurrentPointDTO(Point point){ .point(point.getPoint()) .build(); } - - public static Point savePoint(OrderDto orderDto, User user) { - return Point.builder() - .point(orderDto.getPrice()) - .status(PaymentStatus.PAID) - .user(user) - .build(); - } } diff --git a/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java b/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java index 8778280b..a3c8378a 100644 --- a/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java +++ b/src/main/java/com/otakumap/domain/point/dto/PointResponseDTO.java @@ -1,5 +1,8 @@ package com.otakumap.domain.point.dto; +import com.otakumap.domain.payment.enums.PaymentStatus; +import com.otakumap.domain.point.entity.Point; +import com.otakumap.domain.user.entity.User; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -41,4 +44,33 @@ public static class CurrentPointDTO { String userId; Long point; } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class PointSaveResponseDTO { + private String impUid; + private String merchantUid; + private Long point; + private String chargedBy; + private PaymentStatus status; + private LocalDateTime chargedAt; + + public Point toEntity(User user) { + return Point.builder() + .user(user) + .point(point) + .impUid(impUid) + .merchantUid(merchantUid) + .chargedBy(chargedBy) + .status(status != null ? status : PaymentStatus.PENDING) // 기본값 설정 + .chargedAt(chargedAt != null ? chargedAt : LocalDateTime.now()) // 기본값 설정 + .build(); + } + + public String getImpUid() { + return impUid; + } + } } diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 29c49e19..807bb270 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -32,10 +32,18 @@ public class Point extends BaseEntity { @JoinColumn(name = "user_id", nullable = false) private User user; - // 충전된 포인트 금액 + // 결제 금액 @Column(nullable = false) private Long point; + // 아임포트 결제 고유번호 (imp_uid) + @Column(nullable = false, unique = true) + private String impUid; + + // 주문 고유번호 (merchant_uid) + @Column(nullable = false, unique = true) + private String merchantUid; + // 충전된 시간 @Column(name = "charged_at", nullable = false, updatable = true) @CreationTimestamp diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 757e990a..f9069f25 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -70,6 +70,11 @@ public class User extends BaseEntity { @Column(columnDefinition = "VARCHAR(10) DEFAULT 'USER'", nullable = false) private Role role; + // 현재 유저가 보유한 총 포인트 + @Column(nullable = false) + @ColumnDefault("0") + private Long totalPoint = 0L; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "profile_image_id", referencedColumnName = "id") private Image profileImage; @@ -105,4 +110,16 @@ public void updateEmail(String email) { public void addEarnings(int price) { this.donation += price; } + + public void addPoint(Long point) { + this.totalPoint += point; + } + + public boolean subPoint(Long point) { + if (this.totalPoint >= point) { + this.totalPoint -= point; + return true; + } + return false; + } } From 26a3eb22db4bed968a651e0dd65ae7ac9f0ac229 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 21:33:26 +0900 Subject: [PATCH 507/516] =?UTF-8?q?Refactor:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=A9=EC=A0=84=EB=90=A0=20=EB=95=8C=EB=A7=88=EB=8B=A4=20?= =?UTF-8?q?user=EC=9D=98=20totalPoint=20=EA=B0=92=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=EB=90=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/service/PaymentCommandServiceImpl.java | 2 ++ .../java/com/otakumap/domain/user/entity/User.java | 12 ++---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java index d4d603b3..d265a492 100644 --- a/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/payment/service/PaymentCommandServiceImpl.java @@ -60,6 +60,8 @@ public IamportResponse cancelPayment(String imp_uid) { */ public String savePoint(PointResponseDTO.PointSaveResponseDTO pointResponseDTO, User user){ try { + // 유저의 총 포인트 업데이트 + user.updateTotalPoint(user.getTotalPoint() + pointResponseDTO.getPoint()); pointRepository.save(pointResponseDTO.toEntity(user)); return "주문 정보가 성공적으로 저장되었습니다."; } catch (Exception e) { diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index f9069f25..575b94bd 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -111,15 +111,7 @@ public void addEarnings(int price) { this.donation += price; } - public void addPoint(Long point) { - this.totalPoint += point; - } - - public boolean subPoint(Long point) { - if (this.totalPoint >= point) { - this.totalPoint -= point; - return true; - } - return false; + public void updateTotalPoint(Long newTotalPoint) { + this.totalPoint = newTotalPoint; } } From e9607fb31dddc13e1e7f587322f47b012b8240e4 Mon Sep 17 00:00:00 2001 From: tl1l1l1s Date: Thu, 20 Feb 2025 22:14:24 +0900 Subject: [PATCH 508/516] =?UTF-8?q?Refactor:=20Entity=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=B0=20=ED=9B=84=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/otakumap/domain/point/entity/Point.java | 13 ------------- .../reviews/repository/ReviewRepositoryImpl.java | 10 +++++----- .../java/com/otakumap/domain/user/entity/User.java | 13 +++++++++++++ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 807bb270..b7df0f3e 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -67,19 +67,6 @@ public Point(Long point, LocalDateTime chargedAt, PaymentStatus status, User use this.user = user; } - public void addPoint(Long point) { - this.point += point; - } - - public Long subPoint(Long point) { - this.point -= point; - return this.point; - } - - public boolean isAffordable(Long point) { - return this.point >= point; - } - public void setPoint(Long totalPoint) { this.point = totalPoint; } diff --git a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java index f941d596..049457b1 100644 --- a/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java +++ b/src/main/java/com/otakumap/domain/reviews/repository/ReviewRepositoryImpl.java @@ -197,7 +197,7 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( throw new TransactionHandler(ErrorStatus.PURCHASE_FREE_CONTENT); } // 포인트 부족 확인 - if (!buyerPoint.isAffordable(price)) { + if (!user.isAffordable(price)) { throw new TransactionHandler(ErrorStatus.PURCHASE_INSUFFICIENT_POINTS); } // 글쓴이와 구매자가 다른지 확인 @@ -211,10 +211,10 @@ private ReviewResponseDTO.PurchaseReviewDTO processReviewPurchase( } // 포인트 수정 후 업데이트 - sellerPoint.addPoint(price); - Long remainingPoints = buyerPoint.subPoint(price); - pointRepository.save(sellerPoint); - pointRepository.save(buyerPoint); + seller.addPoint(price); + Long remainingPoints = user.subPoint(price); + userRepository.save(seller); + userRepository.save(user); int priceInt = Math.toIntExact(price); // 거래 내역 저장(사용한 것과 번 것) diff --git a/src/main/java/com/otakumap/domain/user/entity/User.java b/src/main/java/com/otakumap/domain/user/entity/User.java index 575b94bd..5863c39b 100644 --- a/src/main/java/com/otakumap/domain/user/entity/User.java +++ b/src/main/java/com/otakumap/domain/user/entity/User.java @@ -114,4 +114,17 @@ public void addEarnings(int price) { public void updateTotalPoint(Long newTotalPoint) { this.totalPoint = newTotalPoint; } + + public boolean isAffordable(Long point) { + return this.totalPoint >= point; + } + + public Long subPoint(Long point) { + this.totalPoint -= point; + return this.totalPoint; + } + public void addPoint(Long point) { + this.totalPoint += point; + } + } From ed5112cf46480ef54df804eea722deff48ea5b67 Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 22:33:07 +0900 Subject: [PATCH 509/516] =?UTF-8?q?Refactor:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=A9=EC=A0=84=20=EB=82=B4=EC=97=AD=EC=9D=84=20user?= =?UTF-8?q?=EC=9D=98=20totalPoint=20=EC=B9=BC=EB=9F=BC=EA=B0=92=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/point/service/PointQueryserviceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java index d22e9c9e..66ed1e83 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java +++ b/src/main/java/com/otakumap/domain/point/service/PointQueryserviceImpl.java @@ -1,6 +1,5 @@ package com.otakumap.domain.point.service; -import com.otakumap.domain.point.converter.PointConverter; import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; import com.otakumap.domain.point.repository.PointRepository; @@ -22,6 +21,9 @@ public Page getChargePointList(User user, Integer page) { @Override public PointResponseDTO.CurrentPointDTO getCurrentPoint(User user) { - return PointConverter.toCurrentPointDTO(pointRepository.findTopByUserOrderByChargedAtDesc(user)); + return PointResponseDTO.CurrentPointDTO.builder() + .userId(user.getUserId()) + .point(user.getTotalPoint()) // 유저 테이블의 totalPoint 값을 반환 + .build(); } } \ No newline at end of file From aa33b1b718680916fce898a5babb98eaab25d34b Mon Sep 17 00:00:00 2001 From: "[Kkimdoyeon]" <[ehdus5176@naver.com]> Date: Thu, 20 Feb 2025 23:25:13 +0900 Subject: [PATCH 510/516] =?UTF-8?q?Chore:=20=EC=95=88=20=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20import=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/order/dto/OrderDto.java | 19 ------------ .../otakumap/domain/order/entity/Order.java | 29 ------------------- .../order/repository/OrderRepository.java | 7 ----- .../point/controller/PointController.java | 3 -- .../point/converter/PointConverter.java | 2 -- .../otakumap/domain/point/entity/Point.java | 4 --- .../point/service/PointCommandService.java | 2 -- .../user/service/UserCommandServiceImpl.java | 4 --- 8 files changed, 70 deletions(-) delete mode 100644 src/main/java/com/otakumap/domain/order/dto/OrderDto.java delete mode 100644 src/main/java/com/otakumap/domain/order/entity/Order.java delete mode 100644 src/main/java/com/otakumap/domain/order/repository/OrderRepository.java diff --git a/src/main/java/com/otakumap/domain/order/dto/OrderDto.java b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java deleted file mode 100644 index d12479c5..00000000 --- a/src/main/java/com/otakumap/domain/order/dto/OrderDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.otakumap.domain.order.dto; - -//import com.otakumap.domain.order.entity.Order; -//import lombok.Getter; -// -//@Getter -//public class OrderDto { -// Long price; -// String impUid; -// String merchantUid; -// -// public Order toEntity() { -// return Order.builder() -// .price(price) -// .impUid(impUid) -// .merchantUid(merchantUid) -// .build(); -// } -//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/entity/Order.java b/src/main/java/com/otakumap/domain/order/entity/Order.java deleted file mode 100644 index c4f74af4..00000000 --- a/src/main/java/com/otakumap/domain/order/entity/Order.java +++ /dev/null @@ -1,29 +0,0 @@ -//package com.otakumap.domain.order.entity; -// -//import com.otakumap.global.common.BaseEntity; -//import jakarta.persistence.*; -//import lombok.AllArgsConstructor; -//import lombok.Builder; -//import lombok.Getter; -//import lombok.NoArgsConstructor; -// -//@Entity -//@Builder -//@Getter -//@AllArgsConstructor -//@NoArgsConstructor -//@Table(name = "orders") -//public class Order extends BaseEntity { -// @Id -// @GeneratedValue(strategy = GenerationType.IDENTITY) -// private Long id; -// -// @Column(nullable = false) -// private Long price; -// -// @Column(nullable = false) -// private String impUid; -// -// @Column(nullable = false) -// private String merchantUid; -//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java deleted file mode 100644 index 72dab8ac..00000000 --- a/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.otakumap.domain.order.repository; -// -//import com.otakumap.domain.order.entity.Order; -//import org.springframework.data.jpa.repository.JpaRepository; -// -//public interface OrderRepository extends JpaRepository { -//} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/controller/PointController.java b/src/main/java/com/otakumap/domain/point/controller/PointController.java index d1d9118a..b6fd723f 100644 --- a/src/main/java/com/otakumap/domain/point/controller/PointController.java +++ b/src/main/java/com/otakumap/domain/point/controller/PointController.java @@ -25,9 +25,6 @@ public class PointController { @Operation(summary = "포인트 충전", description = "사용자가 포인트를 충전합니다.") @PostMapping("/charge") public ApiResponse processOrder(@RequestBody PointResponseDTO.PointSaveResponseDTO pointResponseDTO, @CurrentUser User user) { - // 구매한 후기 정보를 로그에 출력 - //log.info("Received orders: {}", pointResponseDTO.toString()); - // 성공적으로 받아들였다는 응답 반환 return ApiResponse.onSuccess(paymentCommandService.savePoint(pointResponseDTO, user)); } diff --git a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java index 120bed2a..13ac9bf0 100644 --- a/src/main/java/com/otakumap/domain/point/converter/PointConverter.java +++ b/src/main/java/com/otakumap/domain/point/converter/PointConverter.java @@ -1,9 +1,7 @@ package com.otakumap.domain.point.converter; -import com.otakumap.domain.payment.enums.PaymentStatus; import com.otakumap.domain.point.dto.PointResponseDTO; import com.otakumap.domain.point.entity.Point; -import com.otakumap.domain.user.entity.User; import org.springframework.data.domain.Page; import java.util.List; diff --git a/src/main/java/com/otakumap/domain/point/entity/Point.java b/src/main/java/com/otakumap/domain/point/entity/Point.java index 807bb270..cad8beba 100644 --- a/src/main/java/com/otakumap/domain/point/entity/Point.java +++ b/src/main/java/com/otakumap/domain/point/entity/Point.java @@ -83,8 +83,4 @@ public boolean isAffordable(Long point) { public void setPoint(Long totalPoint) { this.point = totalPoint; } - - public void setChargedAt(LocalDateTime now) { - this.chargedAt = now; - } } \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java index c0aac0d4..0ce0bddb 100644 --- a/src/main/java/com/otakumap/domain/point/service/PointCommandService.java +++ b/src/main/java/com/otakumap/domain/point/service/PointCommandService.java @@ -1,6 +1,4 @@ package com.otakumap.domain.point.service; -import com.otakumap.domain.user.entity.User; - public interface PointCommandService { } diff --git a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java index 87b36f77..ea35c506 100644 --- a/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/com/otakumap/domain/user/service/UserCommandServiceImpl.java @@ -1,6 +1,5 @@ package com.otakumap.domain.user.service; -import com.otakumap.domain.auth.service.MailService; import com.otakumap.domain.image.entity.Image; import com.otakumap.domain.image.service.ImageCommandService; import com.otakumap.domain.user.dto.UserRequestDTO; @@ -10,7 +9,6 @@ import com.otakumap.global.apiPayload.exception.handler.AuthHandler; import com.otakumap.global.apiPayload.exception.handler.UserHandler; import com.otakumap.global.util.EmailUtil; -import com.otakumap.global.util.RedisUtil; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; @@ -24,8 +22,6 @@ public class UserCommandServiceImpl implements UserCommandService { private final EmailUtil emailUtil; private final PasswordEncoder passwordEncoder; private final ImageCommandService imageCommandService; - private final MailService mailService; - private final RedisUtil redisUtil; @Override @Transactional From 6bf435cfa5ac6496fa5e27fe645043ff6916b8d2 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 21 Feb 2025 13:23:38 +0900 Subject: [PATCH 511/516] =?UTF-8?q?Feat:=20type=EB=8F=84=20=EA=B0=99?= =?UTF-8?q?=EC=9D=B4=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/converter/RouteLikeConverter.java | 3 ++- .../otakumap/domain/route_like/dto/RouteLikeResponseDTO.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index c49bb4d5..6641440d 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -1,6 +1,6 @@ package com.otakumap.domain.route_like.converter; -import com.otakumap.domain.event_like.dto.EventLikeResponseDTO; +import com.otakumap.domain.reviews.enums.ReviewType; import com.otakumap.domain.route.entity.Route; import com.otakumap.domain.route_like.dto.RouteLikeResponseDTO; import com.otakumap.domain.route_like.entity.RouteLike; @@ -43,6 +43,7 @@ public static RouteLikeResponseDTO.RouteLikePreViewDTO routeLikePreViewDTO(Route return RouteLikeResponseDTO.RouteLikePreViewDTO.builder() .id(routeLike.getId()) .routeId(routeLike.getRoute().getId()) + .type(routeLike.getRoute().getEventReview() == null ? ReviewType.PLACE : ReviewType.EVENT) .name(routeLike.getRoute().getName()) .isFavorite(routeLike.getIsFavorite()) .build(); diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java index 45fe2970..751ac8b1 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java @@ -1,5 +1,6 @@ package com.otakumap.domain.route_like.dto; +import com.otakumap.domain.reviews.enums.ReviewType; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -44,6 +45,7 @@ public static class RouteLikePreViewDTO { Long id; // RoutelikeId Long routeId; String name; + ReviewType type; Boolean isFavorite; } From 8968c7139b1ece09ebbaf8932d7cf440afd97c56 Mon Sep 17 00:00:00 2001 From: mk-star Date: Fri, 21 Feb 2025 13:44:22 +0900 Subject: [PATCH 512/516] =?UTF-8?q?Feat:=20reviewId=EB=8F=84=20=EA=B0=99?= =?UTF-8?q?=EC=9D=B4=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/route_like/converter/RouteLikeConverter.java | 9 +++++---- .../domain/route_like/dto/RouteLikeResponseDTO.java | 1 + .../route_like/service/RouteLikeQueryServiceImpl.java | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java index 6641440d..d420dc42 100644 --- a/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java +++ b/src/main/java/com/otakumap/domain/route_like/converter/RouteLikeConverter.java @@ -39,12 +39,13 @@ public static RouteLikeResponseDTO.RouteUpdateResultDTO toRouteUpdateResultDTO(R .build(); } - public static RouteLikeResponseDTO.RouteLikePreViewDTO routeLikePreViewDTO(RouteLike routeLike) { + public static RouteLikeResponseDTO.RouteLikePreViewDTO routeLikePreViewDTO(RouteLike routeLike, Route route) { return RouteLikeResponseDTO.RouteLikePreViewDTO.builder() .id(routeLike.getId()) - .routeId(routeLike.getRoute().getId()) - .type(routeLike.getRoute().getEventReview() == null ? ReviewType.PLACE : ReviewType.EVENT) - .name(routeLike.getRoute().getName()) + .routeId(route.getId()) + .reviewId(route.getEventReview() == null ? route.getPlaceReview().getId() : route.getEventReview().getId()) + .type(route.getEventReview() == null ? ReviewType.PLACE : ReviewType.EVENT) + .name(route.getName()) .isFavorite(routeLike.getIsFavorite()) .build(); } diff --git a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java index 751ac8b1..f33e89f9 100644 --- a/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java +++ b/src/main/java/com/otakumap/domain/route_like/dto/RouteLikeResponseDTO.java @@ -45,6 +45,7 @@ public static class RouteLikePreViewDTO { Long id; // RoutelikeId Long routeId; String name; + Long reviewId; ReviewType type; Boolean isFavorite; } diff --git a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java index 03a5002e..bcb48e0b 100644 --- a/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java +++ b/src/main/java/com/otakumap/domain/route_like/service/RouteLikeQueryServiceImpl.java @@ -60,7 +60,7 @@ private RouteLikeResponseDTO.RouteLikePreViewListDTO createRouteLikePreviewListD } List list = routeLikes .stream() - .map(RouteLikeConverter::routeLikePreViewDTO) + .map(routeLike -> RouteLikeConverter.routeLikePreViewDTO(routeLike, routeLike.getRoute())) .collect(Collectors.toList()); return RouteLikeConverter.routeLikePreViewListDTO(list, hasNext, lastId); From 705baed2c8be77a2aaae6e0138127df5260b89d9 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:18:45 +0900 Subject: [PATCH 513/516] =?UTF-8?q?Feat:=20nickname=EC=9D=84=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otakumap/domain/reviews/converter/ReviewConverter.java | 4 ++-- .../com/otakumap/domain/reviews/dto/ReviewResponseDTO.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java index eb0e55a1..499f48cd 100644 --- a/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java +++ b/src/main/java/com/otakumap/domain/reviews/converter/ReviewConverter.java @@ -100,7 +100,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toPlaceReviewDetailDTO(PlaceRevi .filter(Objects::nonNull) .map(ImageConverter::toImageDTO) .toList()) - .userName(placeReview.getUser().getName()) + .nickname(placeReview.getUser().getNickname()) .profileImage(ImageConverter.toImageDTO(placeReview.getUser().getProfileImage())) .createdAt(placeReview.getCreatedAt()) .route(placeReview.getRoutes().isEmpty() ? null : RouteConverter.toRouteDTO(placeReview.getRoutes().get(0))) @@ -119,7 +119,7 @@ public static ReviewResponseDTO.ReviewDetailDTO toEventReviewDetailDTO(EventRevi .filter(Objects::nonNull) .map(ImageConverter::toImageDTO) .toList()) - .userName(eventReview.getUser().getName()) + .nickname(eventReview.getUser().getNickname()) .profileImage(ImageConverter.toImageDTO(eventReview.getUser().getProfileImage())) .createdAt(eventReview.getCreatedAt()) .route(eventReview.getRoutes().isEmpty() ? null : RouteConverter.toRouteDTO(eventReview.getRoutes().get(0))) diff --git a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java index 1b77a1d2..58105b66 100644 --- a/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java +++ b/src/main/java/com/otakumap/domain/reviews/dto/ReviewResponseDTO.java @@ -60,7 +60,7 @@ public static class ReviewDetailDTO { Long price; List reviewImages; - String userName; + String nickname; ImageResponseDTO.ImageDTO profileImage; LocalDateTime createdAt; From 159f8e48e76823d7337de4b20a8e54ae763bf740 Mon Sep 17 00:00:00 2001 From: ParkSenn <102174849+ParkSenn@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:19:47 +0900 Subject: [PATCH 514/516] Chore: add untracked files --- .../otakumap/domain/order/dto/OrderDto.java | 19 ++++++++++++ .../otakumap/domain/order/entity/Order.java | 29 +++++++++++++++++++ .../order/repository/OrderRepository.java | 7 +++++ 3 files changed, 55 insertions(+) create mode 100644 src/main/java/com/otakumap/domain/order/dto/OrderDto.java create mode 100644 src/main/java/com/otakumap/domain/order/entity/Order.java create mode 100644 src/main/java/com/otakumap/domain/order/repository/OrderRepository.java diff --git a/src/main/java/com/otakumap/domain/order/dto/OrderDto.java b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java new file mode 100644 index 00000000..f8240f6d --- /dev/null +++ b/src/main/java/com/otakumap/domain/order/dto/OrderDto.java @@ -0,0 +1,19 @@ +package com.otakumap.domain.order.dto; + +import com.otakumap.domain.order.entity.Order; +import lombok.Getter; + +@Getter +public class OrderDto { + Long price; + String impUid; + String merchantUid; + + public Order toEntity() { + return Order.builder() + .price(price) + .impUid(impUid) + .merchantUid(merchantUid) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/entity/Order.java b/src/main/java/com/otakumap/domain/order/entity/Order.java new file mode 100644 index 00000000..4a30ea89 --- /dev/null +++ b/src/main/java/com/otakumap/domain/order/entity/Order.java @@ -0,0 +1,29 @@ +package com.otakumap.domain.order.entity; + +import com.otakumap.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "orders") +public class Order extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long price; + + @Column(nullable = false) + private String impUid; + + @Column(nullable = false) + private String merchantUid; +} \ No newline at end of file diff --git a/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java new file mode 100644 index 00000000..52ae71dc --- /dev/null +++ b/src/main/java/com/otakumap/domain/order/repository/OrderRepository.java @@ -0,0 +1,7 @@ +package com.otakumap.domain.order.repository; + +import com.otakumap.domain.order.entity.Order; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderRepository extends JpaRepository { +} \ No newline at end of file From 814b9cd356988e5cf81011f40a52354e7d7cdaac Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 9 Mar 2025 12:00:34 +0900 Subject: [PATCH 515/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 27 ++++++++++----------------- Dockerfile | 7 +------ 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2c0cb052..03baa187 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,10 +1,8 @@ name: CI/CD using github actions & docker -# event trigger -# dev 브랜치에 push가 되었을 때 실행 on: push: - branches: [ "dev" ] + branches: [ "dev" ] permissions: contents: read @@ -15,12 +13,11 @@ jobs: steps: ## JDK setting - github actions에서 사용할 JDK 설정 - uses: actions/checkout@v3 - - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' - distribution: 'temurin' + distribution: 'temurin' # https://github.com/actions/setup-java ## gradle caching - 빌드 시간 향상 - name: Gradle Caching @@ -38,10 +35,6 @@ jobs: run: chmod +x gradlew - # gradle build - - name: Build with Gradle - run: ./gradlew build -x test - ## yml 파일 생성 - name: make application.yml if: contains(github.ref, 'dev') @@ -53,13 +46,17 @@ jobs: echo "${{ secrets.APPLICATION_YML }}" >> ./application.yml shell: bash + # gradle build + - name: Build with Gradle + run: ./gradlew build -x test + ## docker build & push to production - name: Docker build & push to prod if: contains(github.ref, 'dev') run: | - docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} - docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest . - docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} . + docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} ## deploy to production - name: Deploy to prod @@ -70,12 +67,8 @@ jobs: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USERNAME }} key: ${{ secrets.EC2_PRIVATE_KEY }} - envs: GITHUB_SHA - port: 22 script: | - sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} sudo docker rm -f $(docker ps -qa) - sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest - sudo docker-compose down + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} sudo docker-compose up -d sudo docker image prune -f \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 72a71439..070774f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,4 @@ FROM openjdk:17-jdk ARG JAR_FILE=./build/libs/*-SNAPSHOT.jar -WORKDIR /app COPY ${JAR_FILE} app.jar - -# application.yml 복사 -COPY ./src/main/resources/application.yml /app/application.yml - -ENTRYPOINT [ "java", "-jar", "/app/app.jar" ] \ No newline at end of file +ENTRYPOINT [ "java", "-jar", "/app.jar" ] \ No newline at end of file From cc4cada00ae290e6ae13fbb377e3780e3fc02799 Mon Sep 17 00:00:00 2001 From: mk-star Date: Sun, 9 Mar 2025 12:03:39 +0900 Subject: [PATCH 516/516] =?UTF-8?q?Feat:=20deploy.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 03baa187..5a35f709 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -70,5 +70,6 @@ jobs: script: | sudo docker rm -f $(docker ps -qa) sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} + sudo docker-compose down sudo docker-compose up -d sudo docker image prune -f \ No newline at end of file