From 3777ebaf7d4e588fd973cbc8cc06a35b3ee1a0a3 Mon Sep 17 00:00:00 2001 From: david-parkk Date: Mon, 23 Feb 2026 19:24:15 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EB=B0=B0=EB=84=88=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 --- .../banner/application/BannerService.java | 58 +++++++++++++++++ .../backend/domain/banner/domain/Banner.java | 28 +++++++++ .../domain/repository/BannerRepository.java | 9 +++ .../domain/banner/dto/GetBannerResponse.java | 4 ++ .../domain/banner/dto/PatchBannerRequest.java | 6 ++ .../banner/dto/PatchBannerRequests.java | 15 +++++ .../banner/presentation/BannerController.java | 46 ++++++++++++++ .../image/application/S3ImageService.java | 63 ++++++++++++++++--- .../place/application/PlaceService.java | 15 ++--- .../global/config/AuthorizationList.java | 3 +- .../status/BaseExceptionResponseStatus.java | 7 ++- 11 files changed, 231 insertions(+), 23 deletions(-) create mode 100644 src/main/java/ku_rum/backend/domain/banner/application/BannerService.java create mode 100644 src/main/java/ku_rum/backend/domain/banner/domain/Banner.java create mode 100644 src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java create mode 100644 src/main/java/ku_rum/backend/domain/banner/dto/GetBannerResponse.java create mode 100644 src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequest.java create mode 100644 src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequests.java create mode 100644 src/main/java/ku_rum/backend/domain/banner/presentation/BannerController.java diff --git a/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java b/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java new file mode 100644 index 00000000..a6d90f3e --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java @@ -0,0 +1,58 @@ +package ku_rum.backend.domain.banner.application; + +import static ku_rum.backend.global.support.status.BaseExceptionResponseStatus.URL_NOT_BANNER; + +import java.util.List; +import ku_rum.backend.domain.banner.domain.Banner; +import ku_rum.backend.domain.banner.domain.repository.BannerRepository; +import ku_rum.backend.domain.banner.dto.GetBannerResponse; +import ku_rum.backend.domain.banner.dto.PatchBannerRequests; +import ku_rum.backend.domain.common.image.application.S3ImageService; +import ku_rum.backend.global.exception.global.GlobalException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor + +public class BannerService { + + private final BannerRepository bannerRepository; + private final S3ImageService s3ImageService; + + public List findBanners() { + List banners = bannerRepository.findAll(); + return banners.stream() + .map(banner -> new GetBannerResponse(banner.getBannerId(), banner.getImageUrl(), + banner.getLink())) + .toList(); + } + + @Transactional + public void addBanner(PatchBannerRequests requests) { + List banners = requests.banners().stream() + .map(request -> { + String imageUrl = s3ImageService.uploadBannerImage(request.images()); + return Banner.builder() + .imageUrl(imageUrl) + .link(request.link()) + .build(); + }) + .toList(); + bannerRepository.saveAll(banners); + } + + @Transactional + public void deleteById(Long bannerId) { + Banner banner = findById(bannerId); + bannerRepository.deleteById(bannerId); + + s3ImageService.deleteBannerImage(banner.getImageUrl()); + } + + private Banner findById(Long bannerId) { + return bannerRepository.findById(bannerId) + .orElseThrow(() -> new GlobalException(URL_NOT_BANNER)); + } +} diff --git a/src/main/java/ku_rum/backend/domain/banner/domain/Banner.java b/src/main/java/ku_rum/backend/domain/banner/domain/Banner.java new file mode 100644 index 00000000..aeac7872 --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/domain/Banner.java @@ -0,0 +1,28 @@ +package ku_rum.backend.domain.banner.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import ku_rum.backend.global.support.type.BaseEntity; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Getter +public class Banner extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long bannerId; + + private String imageUrl; + + private String link; +} diff --git a/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java b/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java new file mode 100644 index 00000000..4b44e9fb --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java @@ -0,0 +1,9 @@ +package ku_rum.backend.domain.banner.domain.repository; + +import ku_rum.backend.domain.banner.domain.Banner; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BannerRepository extends JpaRepository { +} diff --git a/src/main/java/ku_rum/backend/domain/banner/dto/GetBannerResponse.java b/src/main/java/ku_rum/backend/domain/banner/dto/GetBannerResponse.java new file mode 100644 index 00000000..3a15f1e1 --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/dto/GetBannerResponse.java @@ -0,0 +1,4 @@ +package ku_rum.backend.domain.banner.dto; + +public record GetBannerResponse(Long bannerId, String bannerImageUrl, String bannerLink) { +} diff --git a/src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequest.java b/src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequest.java new file mode 100644 index 00000000..2d19ce17 --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequest.java @@ -0,0 +1,6 @@ +package ku_rum.backend.domain.banner.dto; + +import org.springframework.web.multipart.MultipartFile; + +public record PatchBannerRequest(MultipartFile images, String link) { +} diff --git a/src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequests.java b/src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequests.java new file mode 100644 index 00000000..6e900035 --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/dto/PatchBannerRequests.java @@ -0,0 +1,15 @@ +package ku_rum.backend.domain.banner.dto; + +import java.util.List; +import java.util.stream.IntStream; +import org.springframework.web.multipart.MultipartFile; + +public record PatchBannerRequests(List banners) { + + public static PatchBannerRequests of(List images, List links) { + List banners = IntStream.range(0, images.size()) + .mapToObj(i -> new PatchBannerRequest(images.get(i), links.get(i))) + .toList(); + return new PatchBannerRequests(banners); + } +} diff --git a/src/main/java/ku_rum/backend/domain/banner/presentation/BannerController.java b/src/main/java/ku_rum/backend/domain/banner/presentation/BannerController.java new file mode 100644 index 00000000..c25eb241 --- /dev/null +++ b/src/main/java/ku_rum/backend/domain/banner/presentation/BannerController.java @@ -0,0 +1,46 @@ +package ku_rum.backend.domain.banner.presentation; + +import java.util.List; +import ku_rum.backend.domain.banner.application.BannerService; +import ku_rum.backend.domain.banner.dto.GetBannerResponse; +import ku_rum.backend.domain.banner.dto.PatchBannerRequests; +import ku_rum.backend.global.support.response.BaseResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/banner") +public class BannerController { + + private final BannerService bannerService; + + @GetMapping + public BaseResponse> findBanners() { + List response = bannerService.findBanners(); + return BaseResponse.ok(response); + } + + @PatchMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public BaseResponse addBanners( + @RequestParam("images") List bannerImages, + @RequestParam("links") List bannerLinks) { + PatchBannerRequests request = PatchBannerRequests.of(bannerImages, bannerLinks); + bannerService.addBanner(request); + return BaseResponse.ok(); + } + + @DeleteMapping({"/{bannerId}"}) + public BaseResponse deleteBanners(@PathVariable("bannerId") Long bannerId) { + bannerService.deleteById(bannerId); + return BaseResponse.ok(); + } +} diff --git a/src/main/java/ku_rum/backend/domain/common/image/application/S3ImageService.java b/src/main/java/ku_rum/backend/domain/common/image/application/S3ImageService.java index 55efd36c..79dc7216 100644 --- a/src/main/java/ku_rum/backend/domain/common/image/application/S3ImageService.java +++ b/src/main/java/ku_rum/backend/domain/common/image/application/S3ImageService.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; import ku_rum.backend.domain.common.image.domain.vo.FilePath; import ku_rum.backend.global.exception.global.GlobalException; import ku_rum.backend.global.support.status.BaseExceptionResponseStatus; @@ -30,22 +29,37 @@ public class S3ImageService { @Value("${cloud.aws.region.static}") private String region; - private static final String PREFIX = "place-images"; + private static final String PLACE_IMAGE_PREFIX = "place-images"; + private static final String BANNER_PREFIX = "banner"; - public List uploadImages(List images) { + public List uploadPlaceImages(List images) { List imageUrls = new ArrayList<>(); for (MultipartFile image : images) { - String imageUrl = uploadImage(image); + String imageUrl = uploadImage(image, PLACE_IMAGE_PREFIX); + imageUrls.add(imageUrl); + } + return imageUrls; + } + + public String uploadBannerImage(MultipartFile image) { + return uploadImage(image, BANNER_PREFIX); + } + + private List uploadImages(List images, String prefix) { + List imageUrls = new ArrayList<>(); + + for (MultipartFile image : images) { + String imageUrl = uploadImage(image, prefix); imageUrls.add(imageUrl); } return imageUrls; } - public String uploadImage(MultipartFile image) { + private String uploadImage(MultipartFile image, String prefix) { String originalFilename = image.getOriginalFilename(); - String filePath = FilePath.createPath(PREFIX, originalFilename); + String filePath = FilePath.createPath(prefix, originalFilename); try { PutObjectRequest putObjectRequest = PutObjectRequest.builder() @@ -64,12 +78,12 @@ public String uploadImage(MultipartFile image) { return String.format("https://%s.s3.%s.amazonaws.com/%s", bucket, region, filePath); } - public void deleteImage(String imageUrl) { + public void deleteImage(String imageUrl, String prefix) { if (imageUrl == null || imageUrl.isBlank()) { return; } - int prefixIndex = imageUrl.indexOf(PREFIX); + int prefixIndex = imageUrl.indexOf(prefix); if (prefixIndex == -1) { log.warn("삭제할 수 없는 이미지 URL 형식: {}", imageUrl); return; @@ -85,10 +99,39 @@ public void deleteImage(String imageUrl) { s3Client.deleteObject(deleteObjectRequest); } - public void deleteImages(List imageUrls) { + public void deletePlaceImages(List imageUrls) { + for (String url : imageUrls) { + deletePlaceImages(url); + } + } + + public void deletePlaceImages(String imageUrls) { + try { + deleteImage(imageUrls, PLACE_IMAGE_PREFIX); + } catch (Exception e) { + log.warn("Failed to delete S3 image: {}", imageUrls, e); + } + } + + public void deleteBannerImages(List imageUrls) { + for (String url : imageUrls) { + deleteBannerImage(url); + } + } + + public void deleteBannerImage(String imageUrls) { + try { + deleteImage(imageUrls, BANNER_PREFIX); + } catch (Exception e) { + log.warn("Failed to delete S3 image: {}", imageUrls, e); + } + + } + + private void deleteImages(List imageUrls, String prefix) { for (String url : imageUrls) { try { - deleteImage(url); + deleteImage(url, prefix); } catch (Exception e) { log.warn("Failed to delete S3 image: {}", url, e); } diff --git a/src/main/java/ku_rum/backend/domain/place/application/PlaceService.java b/src/main/java/ku_rum/backend/domain/place/application/PlaceService.java index 27cc92ad..fd0360e5 100644 --- a/src/main/java/ku_rum/backend/domain/place/application/PlaceService.java +++ b/src/main/java/ku_rum/backend/domain/place/application/PlaceService.java @@ -164,7 +164,7 @@ public void modifyPlaceImages(Long placeId, List images) { List oldImageUrls = existingImages.stream() .map(PlaceImage::getImageUrl) .toList(); - List newImageUrls = s3ImageService.uploadImages(images); + List newImageUrls = s3ImageService.uploadPlaceImages(images); placeImageRepository.deleteByPlace(place); List newImages = new ArrayList<>(); @@ -179,21 +179,14 @@ public void modifyPlaceImages(Long placeId, List images) { placeImageRepository.saveAll(newImages); - for (String oldUrl : oldImageUrls) { - try { - s3ImageService.deleteImage(oldUrl); - } catch (Exception e) { - log.warn("Failed to delete old S3 image: {}", oldUrl, e); - - } - } + s3ImageService.deletePlaceImages(oldImageUrls); } @Transactional public void addPlaceImages(Long placeId, List images) { Place place = findPlace(placeId); - List newImageUrls = s3ImageService.uploadImages(images); + List newImageUrls = s3ImageService.uploadPlaceImages(images); List newImages = newImageUrls.stream() .map(url -> PlaceImage.builder() @@ -218,7 +211,7 @@ public void deletePlaceImages(Long placeId, Long placeImageId) { placeImageRepository.delete(placeImage); try { - s3ImageService.deleteImage(placeImage.getImageUrl()); + s3ImageService.deletePlaceImages(placeImage.getImageUrl()); } catch (Exception e) { log.warn("Failed to delete S3 image: {}", placeImage.getImageUrl(), e); } diff --git a/src/main/java/ku_rum/backend/global/config/AuthorizationList.java b/src/main/java/ku_rum/backend/global/config/AuthorizationList.java index a1fd9b75..3207a731 100644 --- a/src/main/java/ku_rum/backend/global/config/AuthorizationList.java +++ b/src/main/java/ku_rum/backend/global/config/AuthorizationList.java @@ -36,7 +36,8 @@ public final class AuthorizationList { "/api/v1/notices/**", "/api/v1/alarm/**", "/api/v1/user/**", - "/api/v1/users/social" + "/api/v1/users/social", + "/api/v1/banner/**" ); private AuthorizationList() { diff --git a/src/main/java/ku_rum/backend/global/support/status/BaseExceptionResponseStatus.java b/src/main/java/ku_rum/backend/global/support/status/BaseExceptionResponseStatus.java index 4285bd0d..48a2632c 100644 --- a/src/main/java/ku_rum/backend/global/support/status/BaseExceptionResponseStatus.java +++ b/src/main/java/ku_rum/backend/global/support/status/BaseExceptionResponseStatus.java @@ -148,7 +148,12 @@ public enum BaseExceptionResponseStatus implements ResponseStatus { FCM_SEND_ERROR(1404, HttpStatus.BAD_REQUEST, "알림 전송중 문제가 발생했습니다."), UNSUPPORTED_TOPIC_FCM(1405, HttpStatus.UNPROCESSABLE_ENTITY, "토픽 FCM을 제공하지 않습니다."), UNSUPPORTED_DIRECT_FCM(1406, HttpStatus.UNPROCESSABLE_ENTITY, "Direct FCM을 제공하지 않습니다."), - INVALID_USER_TOKEN(1407, HttpStatus.INTERNAL_SERVER_ERROR, "토큰이 없는 유저가 포함되어 있습니다"); + INVALID_USER_TOKEN(1407, HttpStatus.INTERNAL_SERVER_ERROR, "토큰이 없는 유저가 포함되어 있습니다"), + + /** + * 1500: 배너 + */ + URL_NOT_BANNER(1500, HttpStatus.NOT_FOUND, "찾을 수 없는 배너 입니다."); private final int code; private final HttpStatus status; From 930cea2640f4f024569b2a1cdc737b6e29240958 Mon Sep 17 00:00:00 2001 From: david-parkk Date: Mon, 23 Feb 2026 19:27:57 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EC=BF=BC=EB=A6=AC=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=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 --- .../backend/domain/banner/application/BannerService.java | 2 +- .../domain/banner/domain/repository/BannerRepository.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java b/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java index a6d90f3e..29fac411 100644 --- a/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java +++ b/src/main/java/ku_rum/backend/domain/banner/application/BannerService.java @@ -22,7 +22,7 @@ public class BannerService { private final S3ImageService s3ImageService; public List findBanners() { - List banners = bannerRepository.findAll(); + List banners = bannerRepository.findAllByOrderByCreatedAtDesc(); return banners.stream() .map(banner -> new GetBannerResponse(banner.getBannerId(), banner.getImageUrl(), banner.getLink())) diff --git a/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java b/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java index 4b44e9fb..e94caf34 100644 --- a/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java +++ b/src/main/java/ku_rum/backend/domain/banner/domain/repository/BannerRepository.java @@ -1,9 +1,12 @@ package ku_rum.backend.domain.banner.domain.repository; +import java.util.List; import ku_rum.backend.domain.banner.domain.Banner; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface BannerRepository extends JpaRepository { + + List findAllByOrderByCreatedAtDesc(); } From b2a895a869c0b8ce3cd54a0896cff3b030bfecce Mon Sep 17 00:00:00 2001 From: david-parkk Date: Tue, 24 Feb 2026 15:25:54 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=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 --- .../ku_rum/backend/domain/alarm/dto/response/GetAlarmDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ku_rum/backend/domain/alarm/dto/response/GetAlarmDto.java b/src/main/java/ku_rum/backend/domain/alarm/dto/response/GetAlarmDto.java index 0190e567..711e4b34 100644 --- a/src/main/java/ku_rum/backend/domain/alarm/dto/response/GetAlarmDto.java +++ b/src/main/java/ku_rum/backend/domain/alarm/dto/response/GetAlarmDto.java @@ -28,7 +28,7 @@ public static GetAlarmDto from(Alarm alarm) { public static GetAlarmDto from(UserAnnouncement userAnnouncement) { Announcement announcement = userAnnouncement.getAnnouncement(); return GetAlarmDto.builder() - .id(announcement.getId()) + .id(userAnnouncement.getId()) .alarmType(announcement.getAlarmType()) .alarmCategory(userAnnouncement.getAnnouncement().getAlarmType().getAlarmCategory()) .message(announcement.getMessage())