diff --git a/src/main/java/org/mju_likelion/festival/common/api/ApiPaths.java b/src/main/java/org/mju_likelion/festival/common/api/ApiPaths.java index f936500..ae22603 100644 --- a/src/main/java/org/mju_likelion/festival/common/api/ApiPaths.java +++ b/src/main/java/org/mju_likelion/festival/common/api/ApiPaths.java @@ -16,6 +16,7 @@ public class ApiPaths { private static final String LOST_ITEMS = "/lost-items"; public static final String GET_ALL_LOST_ITEMS = LOST_ITEMS; public static final String SEARCH_LOST_ITEMS = LOST_ITEMS + "/search"; + public static final String GET_LOST_ITEM = LOST_ITEMS + "/{id}"; public static final String POST_LOST_ITEM = LOST_ITEMS; public static final String PATCH_LOST_ITEM = LOST_ITEMS + "/{id}"; public static final String FOUND_LOST_ITEM = LOST_ITEMS + "/{id}/found"; diff --git a/src/main/java/org/mju_likelion/festival/common/authentication/config/AuthenticationConfig.java b/src/main/java/org/mju_likelion/festival/common/authentication/config/AuthenticationConfig.java index 3c88132..60bf26e 100644 --- a/src/main/java/org/mju_likelion/festival/common/authentication/config/AuthenticationConfig.java +++ b/src/main/java/org/mju_likelion/festival/common/authentication/config/AuthenticationConfig.java @@ -3,6 +3,7 @@ import static org.mju_likelion.festival.common.api.ApiPaths.DELETE_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.FOUND_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.GET_BOOTH_MANAGING_DETAIL; +import static org.mju_likelion.festival.common.api.ApiPaths.GET_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.GET_MY_STAMP; import static org.mju_likelion.festival.common.api.ApiPaths.ISSUE_BOOTH_QR; import static org.mju_likelion.festival.common.api.ApiPaths.PATCH_ANNOUNCEMENT; @@ -67,9 +68,9 @@ private void addStudentCouncilAuthenticationInterceptor(final InterceptorRegistr .addPathPatterns(DELETE_LOST_ITEM) .addPathPatterns(POST_LOST_ITEM) .addPathPatterns(PATCH_LOST_ITEM) - .addPathPatterns(DELETE_LOST_ITEM) .addPathPatterns(FOUND_LOST_ITEM) - .excludePathPatterns(SEARCH_LOST_ITEMS); + .excludePathPatterns(SEARCH_LOST_ITEMS) + .excludePathPatterns(GET_LOST_ITEM); } /** diff --git a/src/main/java/org/mju_likelion/festival/lost_item/controller/LostItemController.java b/src/main/java/org/mju_likelion/festival/lost_item/controller/LostItemController.java index dbc7feb..9f82ec8 100644 --- a/src/main/java/org/mju_likelion/festival/lost_item/controller/LostItemController.java +++ b/src/main/java/org/mju_likelion/festival/lost_item/controller/LostItemController.java @@ -3,6 +3,7 @@ import static org.mju_likelion.festival.common.api.ApiPaths.DELETE_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.FOUND_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.GET_ALL_LOST_ITEMS; +import static org.mju_likelion.festival.common.api.ApiPaths.GET_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.PATCH_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.POST_LOST_ITEM; import static org.mju_likelion.festival.common.api.ApiPaths.SEARCH_LOST_ITEMS; @@ -17,6 +18,7 @@ import org.mju_likelion.festival.lost_item.dto.request.CreateLostItemRequest; import org.mju_likelion.festival.lost_item.dto.request.LostItemFoundRequest; import org.mju_likelion.festival.lost_item.dto.request.UpdateLostItemRequest; +import org.mju_likelion.festival.lost_item.dto.response.LostItemDetailResponse; import org.mju_likelion.festival.lost_item.dto.response.SimpleLostItemsResponse; import org.mju_likelion.festival.lost_item.service.LostItemQueryService; import org.mju_likelion.festival.lost_item.service.LostItemService; @@ -58,6 +60,13 @@ public ResponseEntity getLostItems( lostItemQueryService.searchLostItems(SortOrder.fromString(sort), keyword, page, size)); } + @GetMapping(GET_LOST_ITEM) + public ResponseEntity getLostItem( + @PathVariable final UUID id) { + + return ResponseEntity.ok(lostItemQueryService.getLostItem(id)); + } + @PostMapping(POST_LOST_ITEM) public ResponseEntity createLostItem( @RequestBody @Valid final CreateLostItemRequest createLostItemRequest, diff --git a/src/main/java/org/mju_likelion/festival/lost_item/domain/LostItemDetail.java b/src/main/java/org/mju_likelion/festival/lost_item/domain/LostItemDetail.java new file mode 100644 index 0000000..94adddc --- /dev/null +++ b/src/main/java/org/mju_likelion/festival/lost_item/domain/LostItemDetail.java @@ -0,0 +1,30 @@ +package org.mju_likelion.festival.lost_item.domain; + +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class LostItemDetail { + + private final UUID id; + private final String title; + private final String content; + private final String imageUrl; + private final LocalDateTime createdAt; + private final Boolean isFounded; + + @Override + public String toString() { + return "SimpleLostItem{" + + "id=" + id + '\'' + + ", title='" + title + '\'' + + ", content='" + content + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", createdAt=" + createdAt + '\'' + + ", isFounded=" + isFounded + + '}'; + } +} diff --git a/src/main/java/org/mju_likelion/festival/lost_item/domain/SimpleLostItem.java b/src/main/java/org/mju_likelion/festival/lost_item/domain/SimpleLostItem.java index 37b3775..908f1ed 100644 --- a/src/main/java/org/mju_likelion/festival/lost_item/domain/SimpleLostItem.java +++ b/src/main/java/org/mju_likelion/festival/lost_item/domain/SimpleLostItem.java @@ -19,11 +19,11 @@ public class SimpleLostItem { @Override public String toString() { return "SimpleLostItem{" + - "id=" + id + + "id=" + id + '\'' + ", title='" + title + '\'' + ", content='" + content + '\'' + ", imageUrl='" + imageUrl + '\'' + - ", createdAt=" + createdAt + + ", createdAt=" + createdAt + '\'' + ", isFounded=" + isFounded + '}'; } diff --git a/src/main/java/org/mju_likelion/festival/lost_item/domain/repository/LostItemQueryRepository.java b/src/main/java/org/mju_likelion/festival/lost_item/domain/repository/LostItemQueryRepository.java index ad33fe4..85c2800 100644 --- a/src/main/java/org/mju_likelion/festival/lost_item/domain/repository/LostItemQueryRepository.java +++ b/src/main/java/org/mju_likelion/festival/lost_item/domain/repository/LostItemQueryRepository.java @@ -1,11 +1,13 @@ package org.mju_likelion.festival.lost_item.domain.repository; import static org.mju_likelion.festival.common.util.uuid.UUIDUtil.hexToUUID; +import static org.mju_likelion.festival.common.util.uuid.UUIDUtil.uuidToHex; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.mju_likelion.festival.common.enums.SortOrder; +import org.mju_likelion.festival.lost_item.domain.LostItemDetail; import org.mju_likelion.festival.lost_item.domain.SimpleLostItem; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -17,6 +19,7 @@ public class LostItemQueryRepository { private final RowMapper simpleLostItemRowMapper = simpleLostItemRowMapper(); + private final RowMapper lostItemDetailRowMapper = lostItemDetailRowMapper(); private final NamedParameterJdbcTemplate jdbcTemplate; private RowMapper simpleLostItemRowMapper() { @@ -34,6 +37,21 @@ private RowMapper simpleLostItemRowMapper() { }; } + private RowMapper lostItemDetailRowMapper() { + return (rs, rowNum) -> { + String hexId = rs.getString("lostItemId"); + UUID uuid = hexToUUID(hexId); + return new LostItemDetail( + uuid, + rs.getString("title"), + rs.getString("content"), + rs.getString("imageUrl"), + rs.getTimestamp("createdAt").toLocalDateTime(), + rs.getBoolean("isFounded") + ); + }; + } + /** * 페이지네이션을 적용하여 분실물 간단 정보 List 조회. * @@ -128,4 +146,18 @@ public int findTotalPageByKeyword(final String keyword, final int size) { return jdbcTemplate.queryForObject(sql, params, Integer.class); } + + public LostItemDetail findLostItemDetailById(final UUID id) { + String sql = "SELECT HEX(li.id) AS lostItemId, li.title AS title, li.content AS content, " + + "CASE WHEN li.retriever_info IS NULL THEN FALSE ELSE TRUE END AS isFounded, " + + "i.url AS imageUrl, li.created_at AS createdAt " + + "FROM lost_item li " + + "INNER JOIN image i ON li.image_id = i.id " + + "WHERE li.id = UNHEX(:id)"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", uuidToHex(id)); + + return jdbcTemplate.queryForObject(sql, params, lostItemDetailRowMapper); + } } diff --git a/src/main/java/org/mju_likelion/festival/lost_item/dto/response/LostItemDetailResponse.java b/src/main/java/org/mju_likelion/festival/lost_item/dto/response/LostItemDetailResponse.java new file mode 100644 index 0000000..427137c --- /dev/null +++ b/src/main/java/org/mju_likelion/festival/lost_item/dto/response/LostItemDetailResponse.java @@ -0,0 +1,53 @@ +package org.mju_likelion.festival.lost_item.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.mju_likelion.festival.lost_item.domain.LostItemDetail; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor +public class LostItemDetailResponse { + + private UUID id; + private String title; + private String content; + private String imageUrl; + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime createdAt; + private Boolean isFounded; + + public static LostItemDetailResponse from(final LostItemDetail lostItemDetail) { + return new LostItemDetailResponse( + lostItemDetail.getId(), + lostItemDetail.getTitle(), + lostItemDetail.getContent(), + lostItemDetail.getImageUrl(), + lostItemDetail.getCreatedAt(), + lostItemDetail.getIsFounded() + ); + } + + @Override + public String toString() { + return "SimpleLostItem{" + + "id=" + id + '\'' + + ", title='" + title + '\'' + + ", content='" + content + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", createdAt=" + createdAt + '\'' + + ", isFounded=" + isFounded + + '}'; + } +} diff --git a/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemQueryService.java b/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemQueryService.java index 899b5d1..071acf8 100644 --- a/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemQueryService.java +++ b/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemQueryService.java @@ -9,10 +9,13 @@ import org.mju_likelion.festival.common.enums.SortOrder; import org.mju_likelion.festival.common.exception.NotFoundException; import org.mju_likelion.festival.lost_item.domain.LostItem; +import org.mju_likelion.festival.lost_item.domain.LostItemDetail; import org.mju_likelion.festival.lost_item.domain.SimpleLostItem; import org.mju_likelion.festival.lost_item.domain.repository.LostItemJpaRepository; import org.mju_likelion.festival.lost_item.domain.repository.LostItemQueryRepository; +import org.mju_likelion.festival.lost_item.dto.response.LostItemDetailResponse; import org.mju_likelion.festival.lost_item.dto.response.SimpleLostItemsResponse; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -55,6 +58,14 @@ public SimpleLostItemsResponse searchLostItems( return SimpleLostItemsResponse.of(simpleLostItems, totalPage); } + @Cacheable(value = "lostItem", key = "#lostItemId") + public LostItemDetailResponse getLostItem(final UUID lostItemId) { + validateLostItemExistence(lostItemId); + LostItemDetail lostItemDetail = lostItemQueryRepository.findLostItemDetailById(lostItemId); + + return LostItemDetailResponse.from(lostItemDetail); + } + public LostItem getExistLostItem(final UUID lostItemId) { return lostItemJpaRepository.findById(lostItemId) .orElseThrow(() -> new NotFoundException(LOST_ITEM_NOT_FOUND_ERROR)); @@ -65,4 +76,10 @@ public void validatePage(final int page, final int totalPage) { throw new NotFoundException(PAGE_OUT_OF_BOUND_ERROR); } } + + public void validateLostItemExistence(final UUID lostItemId) { + if (!lostItemJpaRepository.existsById(lostItemId)) { + throw new NotFoundException(LOST_ITEM_NOT_FOUND_ERROR); + } + } } diff --git a/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemService.java b/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemService.java index 36633df..c1e0fda 100644 --- a/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemService.java +++ b/src/main/java/org/mju_likelion/festival/lost_item/service/LostItemService.java @@ -12,6 +12,7 @@ import org.mju_likelion.festival.lost_item.dto.request.CreateLostItemRequest; import org.mju_likelion.festival.lost_item.dto.request.LostItemFoundRequest; import org.mju_likelion.festival.lost_item.dto.request.UpdateLostItemRequest; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,6 +41,7 @@ public void createLostItem( lostItemJpaRepository.save(lostItem); } + @CacheEvict(value = "lostItem", key = "#lostItemId") public void updateLostItem( final UUID lostItemId, final UpdateLostItemRequest updateLostItemRequest, @@ -53,6 +55,7 @@ public void updateLostItem( lostItemJpaRepository.save(lostItem); } + @CacheEvict(value = "lostItem", key = "#lostItemId") public void foundLostItem( final UUID lostItemId, final LostItemFoundRequest lostItemFoundRequest, @@ -66,6 +69,7 @@ public void foundLostItem( lostItemJpaRepository.save(lostItem); } + @CacheEvict(value = "lostItem", key = "#lostItemId") public void deleteLostItem(final UUID lostItemId, final UUID studentCouncilId) { LostItem lostItem = lostItemQueryService.getExistLostItem(lostItemId); adminQueryService.validateAdminExistence(studentCouncilId);