From 8f3a2b49528d7f8c9d08f58d044e44a9afbacbb3 Mon Sep 17 00:00:00 2001 From: danbaram0420 Date: Wed, 4 Feb 2026 01:35:35 +0900 Subject: [PATCH 1/2] =?UTF-8?q?ble=20imageurl=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20place=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ble/dto/response/BLEDeviceListRes.java | 5 +- .../domain/ble/service/BLEService.java | 4 +- .../controller/AdminPlaceImageController.java | 100 ++++++++++++++++ .../dto/response/DeletePlaceImageRes.java | 10 ++ .../dto/response/ModifyPlaceImageRes.java | 17 +++ .../place/dto/response/SavePlaceImageRes.java | 17 +++ .../dto/response/SearchPlaceImageRes.java | 25 ++++ .../place/service/AdminPlaceImageService.java | 113 ++++++++++++++++++ .../teamcback/global/response/ResultCode.java | 1 + 9 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java create mode 100644 src/main/java/devkor/com/teamcback/domain/place/dto/response/DeletePlaceImageRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/place/dto/response/ModifyPlaceImageRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/place/dto/response/SavePlaceImageRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java diff --git a/src/main/java/devkor/com/teamcback/domain/ble/dto/response/BLEDeviceListRes.java b/src/main/java/devkor/com/teamcback/domain/ble/dto/response/BLEDeviceListRes.java index 67b68d35..8484a8df 100644 --- a/src/main/java/devkor/com/teamcback/domain/ble/dto/response/BLEDeviceListRes.java +++ b/src/main/java/devkor/com/teamcback/domain/ble/dto/response/BLEDeviceListRes.java @@ -1,5 +1,6 @@ package devkor.com.teamcback.domain.ble.dto.response; +import devkor.com.teamcback.domain.place.entity.Place; import lombok.Getter; import lombok.Setter; @@ -11,11 +12,13 @@ public class BLEDeviceListRes { private String deviceName; private Long placeId; private Integer capacity; + private String imageUrl; - public BLEDeviceListRes(Long id, String deviceName, Long placeId, int capacity) { + public BLEDeviceListRes(Long id, String deviceName, Long placeId, int capacity, String imageUrl) { this.id = id; this.deviceName = deviceName; this.placeId = placeId; this.capacity = capacity; + this.imageUrl = imageUrl; } } \ No newline at end of file diff --git a/src/main/java/devkor/com/teamcback/domain/ble/service/BLEService.java b/src/main/java/devkor/com/teamcback/domain/ble/service/BLEService.java index 9df6c4eb..b1573f3a 100644 --- a/src/main/java/devkor/com/teamcback/domain/ble/service/BLEService.java +++ b/src/main/java/devkor/com/teamcback/domain/ble/service/BLEService.java @@ -175,11 +175,13 @@ public List getBLEDeviceList() { for (BLEDevice device : devices) { Long placeId = null; + String imageUrl = null; if (device.getPlace() != null) { placeId = device.getPlace().getId(); + imageUrl = device.getPlace().getImageUrl(); } - BLEDeviceListRes dto = new BLEDeviceListRes(device.getId(), device.getDeviceName(), placeId, device.getCapacity()); + BLEDeviceListRes dto = new BLEDeviceListRes(device.getId(), device.getDeviceName(), placeId, device.getCapacity(), imageUrl); result.add(dto); } diff --git a/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java b/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java new file mode 100644 index 00000000..9a8bfd43 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java @@ -0,0 +1,100 @@ +package devkor.com.teamcback.domain.place.controller; + +import devkor.com.teamcback.domain.place.dto.response.DeletePlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.ModifyPlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.SavePlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.SearchPlaceImageRes; +import devkor.com.teamcback.domain.place.service.AdminPlaceImageService; +import devkor.com.teamcback.global.response.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +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.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +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/admin/places") +public class AdminPlaceImageController { + private final AdminPlaceImageService adminPlaceImageService; + + @PostMapping(value = "/{placeId}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "장소 사진 저장", + description = "장소 사진 저장") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "409", description = "해당 장소의 이미지가 이미 존재합니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "권한이 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse savePlaceImage( + @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId, + @Parameter(description = "저장할 사진 파일") @RequestPart("image") MultipartFile image + ) { + return CommonResponse.success(adminPlaceImageService.savePlaceImage(placeId, image)); + } + + @PutMapping(value = "/{placeId}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "장소 사진 수정", + description = "장소 사진 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "권한이 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse modifyPlaceImage( + @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId, + @Parameter(description = "수정할 사진 파일") @RequestPart("image") MultipartFile image + ) { + return CommonResponse.success(adminPlaceImageService.modifyPlaceImage(placeId, image)); + } + + @DeleteMapping(value = "/{placeId}/image") + @Operation(summary = "장소 사진 삭제", + description = "장소 사진 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "권한이 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse deletePlaceImage( + @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId + ) { + return CommonResponse.success(adminPlaceImageService.deletePlaceImage(placeId)); + } + + @GetMapping("/{placeId}/image") + @Operation(summary = "장소 사진 검색", + description = "장소 사진 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "권한이 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse getPlaceImage( + @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId + ) { + return CommonResponse.success(adminPlaceImageService.searchPlaceImage(placeId)); + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/DeletePlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/DeletePlaceImageRes.java new file mode 100644 index 00000000..ccb97f80 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/DeletePlaceImageRes.java @@ -0,0 +1,10 @@ +package devkor.com.teamcback.domain.place.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "장소 사진 삭제 응답 dto") +@JsonIgnoreProperties +public class DeletePlaceImageRes { + +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/ModifyPlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/ModifyPlaceImageRes.java new file mode 100644 index 00000000..6b296705 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/ModifyPlaceImageRes.java @@ -0,0 +1,17 @@ +package devkor.com.teamcback.domain.place.dto.response; + +import devkor.com.teamcback.domain.place.entity.Place; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Schema(description = "장소 사진 수정 완료") +@Getter +public class ModifyPlaceImageRes { + private Long placeId; + private String imageUrl; + + public ModifyPlaceImageRes(Place place, String imageUrl) { + this.placeId = place.getId(); + this.imageUrl = imageUrl; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/SavePlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SavePlaceImageRes.java new file mode 100644 index 00000000..1f0c907d --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SavePlaceImageRes.java @@ -0,0 +1,17 @@ +package devkor.com.teamcback.domain.place.dto.response; + +import devkor.com.teamcback.domain.place.entity.Place; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Schema(description = "장소 사진 저장 완료") +@Getter +public class SavePlaceImageRes { + private Long placeId; + private String imageUrl; + + public SavePlaceImageRes(Place place, String imageUrl) { + this.placeId = place.getId(); + this.imageUrl = imageUrl; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java new file mode 100644 index 00000000..4e292296 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java @@ -0,0 +1,25 @@ +package devkor.com.teamcback.domain.place.dto.response; + +import devkor.com.teamcback.domain.place.entity.Place; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Schema(description = "장소 사진 검색 응답 dto") +@Getter +public class SearchPlaceImageRes { + private Long placeId; + private String placeName; + private String image; + + public SearchPlaceImageRes(Place place) { + this.placeId = place.getId(); + this.placeName = place.getName(); + this.image = place.getImageUrl(); + } + + public SearchPlaceImageRes(Place place, String image) { + this.placeId = place.getId(); + this.placeName = place.getName(); + this.image = image; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java b/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java new file mode 100644 index 00000000..1f4696a3 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java @@ -0,0 +1,113 @@ +package devkor.com.teamcback.domain.place.service; + +import static devkor.com.teamcback.global.response.ResultCode.DUPLICATED_PLACE_IMAGE; +import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_FILE; +import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_PLACE; + +import devkor.com.teamcback.domain.common.util.FileUtil; +import devkor.com.teamcback.domain.place.dto.response.DeletePlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.ModifyPlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.SavePlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.SearchPlaceImageRes; +import devkor.com.teamcback.domain.place.entity.Place; +import devkor.com.teamcback.domain.place.repository.PlaceRepository; +import devkor.com.teamcback.global.exception.exception.GlobalException; +import devkor.com.teamcback.infra.s3.FilePath; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +public class AdminPlaceImageService { + private final PlaceRepository placeRepository; + private final FileUtil fileUtil; + + // 장소 사진 저장 + @Transactional + public SavePlaceImageRes savePlaceImage(Long placeId, MultipartFile image) { + Place place = findPlace(placeId); + + // 이미 이미지가 있는지 확인 + checkExistedImage(place); + + // UUID 생성 및 파일 업로드 + String fileUuid = fileUtil.createFileUuid(); + fileUtil.upload(image, fileUuid, FilePath.PLACE, 1L); + + // Place에 fileUuid 저장 + place.setFileUuid(fileUuid); + + String imageUrl = fileUtil.getOriginalFile(fileUuid); + + return new SavePlaceImageRes(place, imageUrl); + } + + // 장소 사진 수정 + @Transactional + public ModifyPlaceImageRes modifyPlaceImage(Long placeId, MultipartFile image) { + Place place = findPlace(placeId); + + // fileUuid 확인 + if (place.getFileUuid() == null) { + place.setFileUuid(fileUtil.createFileUuid()); + } else { + // 기존 파일 삭제 + fileUtil.deleteFile(place.getFileUuid()); + } + + // 새 파일 업로드 + fileUtil.upload(image, place.getFileUuid(), FilePath.PLACE, 1L); + + String imageUrl = fileUtil.getOriginalFile(place.getFileUuid()); + + return new ModifyPlaceImageRes(place, imageUrl); + } + + // 장소 사진 삭제 + @Transactional + public DeletePlaceImageRes deletePlaceImage(Long placeId) { + Place place = findPlace(placeId); + + if (place.getFileUuid() == null) { + throw new GlobalException(NOT_FOUND_FILE); + } + + // 기존 파일 삭제 + fileUtil.deleteFile(place.getFileUuid()); + + // fileUuid 초기화 + place.setFileUuid(null); + + return new DeletePlaceImageRes(); + } + + // 장소 사진 검색 + @Transactional(readOnly = true) + public SearchPlaceImageRes searchPlaceImage(Long placeId) { + Place place = findPlace(placeId); + + if (place.getFileUuid() == null) { + return new SearchPlaceImageRes(place); + } + + String fileName = fileUtil.getOriginalFile(place.getFileUuid()); + if (fileName == null) { + return new SearchPlaceImageRes(place); + } + + return new SearchPlaceImageRes(place, fileName); + } + + private Place findPlace(Long placeId) { + return placeRepository.findById(placeId) + .orElseThrow(() -> new GlobalException(NOT_FOUND_PLACE)); + } + + private void checkExistedImage(Place place) { + if (place.getFileUuid() != null) { + throw new GlobalException(DUPLICATED_PLACE_IMAGE); + } + } +} diff --git a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java index 2e79b076..9fb13a49 100644 --- a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java +++ b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java @@ -46,6 +46,7 @@ public enum ResultCode { NOT_FOUND_PLACE(HttpStatus.NOT_FOUND, 4000, "장소를 찾을 수 없습니다."), NOT_FOUND_PLACE_NICKNAME(HttpStatus.NOT_FOUND, 4001, "장소 별명을 찾을 수 없습니다."), NOT_SUPPORTED_PLACE_TYPE(HttpStatus.BAD_REQUEST, 4002, "지원하지 않는 장소 타입입니다."), + DUPLICATED_PLACE_IMAGE(HttpStatus.CONFLICT, 4003, "해당 시설의 이미지가 이미 존재합니다."), // 길찾기 6000번대 NOT_FOUND_NODE(HttpStatus.NOT_FOUND, 6000, "노드를 찾을 수 없습니다."), From 68bd35790ce36b9ce26303b523b474149ee8260f Mon Sep 17 00:00:00 2001 From: yejin Date: Wed, 4 Feb 2026 14:02:56 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=20=EC=97=AC=EB=9F=AC=20=EC=9E=A5=20=EC=88=98=EC=A0=95?= =?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 --- .../controller/AdminPlaceController.java | 30 +---- .../controller/AdminPlaceImageController.java | 66 ++++++----- .../place/dto/response/GetPlaceImageRes.java | 25 +++++ .../dto/response/SearchPlaceImageListRes.java | 21 ++++ .../dto/response/SearchPlaceImageRes.java | 11 +- .../place/service/AdminPlaceImageService.java | 105 +++++++++++------- .../place/service/AdminPlaceService.java | 25 +---- .../dto/response/SavePlaceImageRes.java | 9 -- 8 files changed, 156 insertions(+), 136 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/place/dto/response/GetPlaceImageRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageListRes.java delete mode 100644 src/main/java/devkor/com/teamcback/domain/suggestion/dto/response/SavePlaceImageRes.java diff --git a/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceController.java b/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceController.java index 2f6a3c1c..bf91b19c 100644 --- a/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceController.java +++ b/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceController.java @@ -2,12 +2,8 @@ import devkor.com.teamcback.domain.place.dto.request.CreatePlaceReq; import devkor.com.teamcback.domain.place.dto.request.ModifyPlaceReq; -import devkor.com.teamcback.domain.place.dto.response.CreatePlaceRes; -import devkor.com.teamcback.domain.place.dto.response.DeletePlaceRes; -import devkor.com.teamcback.domain.place.dto.response.GetPlaceListRes; -import devkor.com.teamcback.domain.place.dto.response.ModifyPlaceRes; +import devkor.com.teamcback.domain.place.dto.response.*; import devkor.com.teamcback.domain.place.service.AdminPlaceService; -import devkor.com.teamcback.domain.suggestion.dto.response.SavePlaceImageRes; import devkor.com.teamcback.global.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -18,9 +14,6 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; @RestController @RequiredArgsConstructor @@ -89,25 +82,4 @@ public CommonResponse deletePlace( @Parameter(description = "삭제할 편의시설 ID") @PathVariable Long placeId) { return CommonResponse.success(adminPlaceService.deletePlace(placeId)); } - -// @PostMapping(value = "/{placeId}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @Operation(summary = "장소 사진 저장", description = "장소 사진 저장(첫 사진이 대표 사진)") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), - @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "401", description = "권한이 없습니다.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "403", description = "잘못된 입력입니다.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - }) - public CommonResponse savePlaceImages( - @Parameter(name = "placeId", description = "장소 ID") - @PathVariable Long placeId, - @Parameter(description = "저장할 사진 파일") - @RequestPart("images") List images - ) { - return CommonResponse.success( - adminPlaceService.savePlaceImage(placeId, images)); - } } diff --git a/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java b/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java index 9a8bfd43..8e4203aa 100644 --- a/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java +++ b/src/main/java/devkor/com/teamcback/domain/place/controller/AdminPlaceImageController.java @@ -1,9 +1,6 @@ package devkor.com.teamcback.domain.place.controller; -import devkor.com.teamcback.domain.place.dto.response.DeletePlaceImageRes; -import devkor.com.teamcback.domain.place.dto.response.ModifyPlaceImageRes; -import devkor.com.teamcback.domain.place.dto.response.SavePlaceImageRes; -import devkor.com.teamcback.domain.place.dto.response.SearchPlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.*; import devkor.com.teamcback.domain.place.service.AdminPlaceImageService; import devkor.com.teamcback.global.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -18,12 +15,13 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; 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; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/api/admin/places") @@ -31,14 +29,12 @@ public class AdminPlaceImageController { private final AdminPlaceImageService adminPlaceImageService; @PostMapping(value = "/{placeId}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @Operation(summary = "장소 사진 저장", - description = "장소 사진 저장") + @Operation(summary = "장소 사진 1장 추가", + description = "장소 사진 1장 추가 저장") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "409", description = "해당 장소의 이미지가 이미 존재합니다.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), @ApiResponse(responseCode = "401", description = "권한이 없습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) @@ -49,26 +45,26 @@ public CommonResponse savePlaceImage( return CommonResponse.success(adminPlaceImageService.savePlaceImage(placeId, image)); } - @PutMapping(value = "/{placeId}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @Operation(summary = "장소 사진 수정", - description = "장소 사진 수정") + @PostMapping(value = "/{placeId}/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "장소 사진 여러장 새로 저장", + description = "기존 장소 사진 전체 삭제 후 사진 여러장 새로 저장") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), - @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - @ApiResponse(responseCode = "401", description = "권한이 없습니다.", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "권한이 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - public CommonResponse modifyPlaceImage( - @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId, - @Parameter(description = "수정할 사진 파일") @RequestPart("image") MultipartFile image + public CommonResponse savePlaceImageList( + @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId, + @Parameter(description = "저장할 사진 파일 목록") @RequestPart("image") List images ) { - return CommonResponse.success(adminPlaceImageService.modifyPlaceImage(placeId, image)); + return CommonResponse.success(adminPlaceImageService.savePlaceImageList(placeId, images)); } - @DeleteMapping(value = "/{placeId}/image") - @Operation(summary = "장소 사진 삭제", - description = "장소 사진 삭제") + @DeleteMapping(value = "/{placeId}/images") + @Operation(summary = "장소 사진 전체 삭제", + description = "장소 사진 전체 삭제") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", @@ -83,8 +79,8 @@ public CommonResponse deletePlaceImage( } @GetMapping("/{placeId}/image") - @Operation(summary = "장소 사진 검색", - description = "장소 사진 검색") + @Operation(summary = "장소 사진 1장 검색", + description = "장소 사진 1장 검색") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", @@ -92,9 +88,25 @@ public CommonResponse deletePlaceImage( @ApiResponse(responseCode = "401", description = "권한이 없습니다.", content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - public CommonResponse getPlaceImage( + public CommonResponse getPlaceImage( @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId ) { return CommonResponse.success(adminPlaceImageService.searchPlaceImage(placeId)); } + + @GetMapping("/{placeId}/images") + @Operation(summary = "장소 사진 여러 장 검색", + description = "장소 사진 여러 장 검색") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "장소를 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "권한이 없습니다.", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse getPlaceImageList( + @Parameter(name = "placeId", description = "장소 ID") @PathVariable Long placeId + ) { + return CommonResponse.success(adminPlaceImageService.searchPlaceImageList(placeId)); + } } diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/GetPlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/GetPlaceImageRes.java new file mode 100644 index 00000000..c6b42dd0 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/GetPlaceImageRes.java @@ -0,0 +1,25 @@ +package devkor.com.teamcback.domain.place.dto.response; + +import devkor.com.teamcback.domain.place.entity.Place; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Schema(description = "장소 사진 검색 응답 dto") +@Getter +public class GetPlaceImageRes { + private Long placeId; + private String placeName; + private String image; + + public GetPlaceImageRes(Place place) { + this.placeId = place.getId(); + this.placeName = place.getName(); + this.image = place.getImageUrl(); + } + + public GetPlaceImageRes(Place place, String image) { + this.placeId = place.getId(); + this.placeName = place.getName(); + this.image = image; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageListRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageListRes.java new file mode 100644 index 00000000..2a8a7e26 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageListRes.java @@ -0,0 +1,21 @@ +package devkor.com.teamcback.domain.place.dto.response; + +import devkor.com.teamcback.domain.place.entity.Place; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.util.List; + +@Schema(description = "장소 사진 리스트 응답 dto") +@Getter +public class SearchPlaceImageListRes { + private Long placeId; + private String placeName; + private List imageList; + + public SearchPlaceImageListRes(Place place, List imageList) { + this.placeId = place.getId(); + this.placeName = place.getName(); + this.imageList = imageList; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java index 4e292296..12a286be 100644 --- a/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java +++ b/src/main/java/devkor/com/teamcback/domain/place/dto/response/SearchPlaceImageRes.java @@ -7,19 +7,16 @@ @Schema(description = "장소 사진 검색 응답 dto") @Getter public class SearchPlaceImageRes { - private Long placeId; - private String placeName; private String image; + private Long sortNum; public SearchPlaceImageRes(Place place) { - this.placeId = place.getId(); - this.placeName = place.getName(); this.image = place.getImageUrl(); + this.sortNum = 1L; } - public SearchPlaceImageRes(Place place, String image) { - this.placeId = place.getId(); - this.placeName = place.getName(); + public SearchPlaceImageRes(String image, Long sortNum) { this.image = image; + this.sortNum = sortNum; } } diff --git a/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java b/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java index 1f4696a3..be3a80c7 100644 --- a/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java +++ b/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceImageService.java @@ -1,14 +1,11 @@ package devkor.com.teamcback.domain.place.service; -import static devkor.com.teamcback.global.response.ResultCode.DUPLICATED_PLACE_IMAGE; import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_FILE; import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_PLACE; +import devkor.com.teamcback.domain.common.entity.File; import devkor.com.teamcback.domain.common.util.FileUtil; -import devkor.com.teamcback.domain.place.dto.response.DeletePlaceImageRes; -import devkor.com.teamcback.domain.place.dto.response.ModifyPlaceImageRes; -import devkor.com.teamcback.domain.place.dto.response.SavePlaceImageRes; -import devkor.com.teamcback.domain.place.dto.response.SearchPlaceImageRes; +import devkor.com.teamcback.domain.place.dto.response.*; import devkor.com.teamcback.domain.place.entity.Place; import devkor.com.teamcback.domain.place.repository.PlaceRepository; import devkor.com.teamcback.global.exception.exception.GlobalException; @@ -18,51 +15,61 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.util.ArrayList; +import java.util.List; + @Service @RequiredArgsConstructor public class AdminPlaceImageService { private final PlaceRepository placeRepository; private final FileUtil fileUtil; - // 장소 사진 저장 + // 장소 사진 1장 추가 저장 @Transactional public SavePlaceImageRes savePlaceImage(Long placeId, MultipartFile image) { Place place = findPlace(placeId); - // 이미 이미지가 있는지 확인 - checkExistedImage(place); + String fileUuid = place.getFileUuid(); + long sortNum = 1L; - // UUID 생성 및 파일 업로드 - String fileUuid = fileUtil.createFileUuid(); - fileUtil.upload(image, fileUuid, FilePath.PLACE, 1L); + // fileUuid 확인 + if (fileUuid == null) { + // 새로 생성하여 저장 + fileUuid = fileUtil.createFileUuid(); + place.setFileUuid(fileUuid); + } + else { + sortNum = fileUtil.getFiles(fileUuid).size() + 1; + } - // Place에 fileUuid 저장 - place.setFileUuid(fileUuid); + // 파일 업로드 + fileUtil.upload(image, fileUuid, FilePath.PLACE, sortNum); String imageUrl = fileUtil.getOriginalFile(fileUuid); return new SavePlaceImageRes(place, imageUrl); } - // 장소 사진 수정 + // 장소 사진 여러 장 새로 저장 @Transactional - public ModifyPlaceImageRes modifyPlaceImage(Long placeId, MultipartFile image) { + public SavePlaceImageRes savePlaceImageList(Long placeId, List images) { Place place = findPlace(placeId); + String fileUuid = place.getFileUuid(); + // fileUuid 확인 - if (place.getFileUuid() == null) { - place.setFileUuid(fileUtil.createFileUuid()); - } else { - // 기존 파일 삭제 - fileUtil.deleteFile(place.getFileUuid()); + if (fileUuid == null) { + // 새로 생성하여 저장 + fileUuid = fileUtil.createFileUuid(); + place.setFileUuid(fileUuid); } - // 새 파일 업로드 - fileUtil.upload(image, place.getFileUuid(), FilePath.PLACE, 1L); + // 파일 업로드 + fileUtil.upload(images, fileUuid, null, FilePath.PLACE); - String imageUrl = fileUtil.getOriginalFile(place.getFileUuid()); + String imageUrl = fileUtil.getOriginalFile(fileUuid); - return new ModifyPlaceImageRes(place, imageUrl); + return new SavePlaceImageRes(place, imageUrl); } // 장소 사진 삭제 @@ -77,37 +84,55 @@ public DeletePlaceImageRes deletePlaceImage(Long placeId) { // 기존 파일 삭제 fileUtil.deleteFile(place.getFileUuid()); - // fileUuid 초기화 - place.setFileUuid(null); - return new DeletePlaceImageRes(); } - // 장소 사진 검색 + // 장소 사진 1장 검색 @Transactional(readOnly = true) - public SearchPlaceImageRes searchPlaceImage(Long placeId) { + public GetPlaceImageRes searchPlaceImage(Long placeId) { Place place = findPlace(placeId); - if (place.getFileUuid() == null) { - return new SearchPlaceImageRes(place); + String fileUuid = place.getFileUuid(); + + // 사진 없을 때 + if (fileUuid == null) { + return new GetPlaceImageRes(place); } - String fileName = fileUtil.getOriginalFile(place.getFileUuid()); + String fileName = fileUtil.getOriginalFile(fileUuid); if (fileName == null) { - return new SearchPlaceImageRes(place); + return new GetPlaceImageRes(place); } - return new SearchPlaceImageRes(place, fileName); + // 사진 있을 때 + return new GetPlaceImageRes(place, fileName); + } + + // 장소 사진 여러 장 검색 + @Transactional(readOnly = true) + public SearchPlaceImageListRes searchPlaceImageList(Long placeId) { + Place place = findPlace(placeId); + + String fileUuid = place.getFileUuid(); + + List images = new ArrayList<>(); + + // 사진 없을 때 + if (fileUuid == null || fileUtil.getFiles(fileUuid).isEmpty()) { + images.add(new SearchPlaceImageRes(place)); + return new SearchPlaceImageListRes(place, images); + } + + // 사진 있을 때 + for(File file : fileUtil.getFiles(fileUuid)) { + images.add(new SearchPlaceImageRes(file.getFileSavedName(), file.getSortNum())); + } + + return new SearchPlaceImageListRes(place, images); } private Place findPlace(Long placeId) { return placeRepository.findById(placeId) .orElseThrow(() -> new GlobalException(NOT_FOUND_PLACE)); } - - private void checkExistedImage(Place place) { - if (place.getFileUuid() != null) { - throw new GlobalException(DUPLICATED_PLACE_IMAGE); - } - } } diff --git a/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceService.java b/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceService.java index b04ea9b3..48792fd1 100644 --- a/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceService.java +++ b/src/main/java/devkor/com/teamcback/domain/place/service/AdminPlaceService.java @@ -1,27 +1,19 @@ package devkor.com.teamcback.domain.place.service; -import devkor.com.teamcback.domain.building.dto.response.SaveBuildingMainImageRes; import devkor.com.teamcback.domain.common.util.FileUtil; import devkor.com.teamcback.domain.place.dto.request.CreatePlaceReq; import devkor.com.teamcback.domain.place.dto.request.ModifyPlaceReq; import devkor.com.teamcback.domain.building.entity.Building; import devkor.com.teamcback.domain.building.repository.BuildingRepository; -import devkor.com.teamcback.domain.place.dto.response.CreatePlaceRes; -import devkor.com.teamcback.domain.place.dto.response.DeletePlaceRes; -import devkor.com.teamcback.domain.place.dto.response.GetPlaceListRes; -import devkor.com.teamcback.domain.place.dto.response.GetPlaceRes; -import devkor.com.teamcback.domain.place.dto.response.ModifyPlaceRes; +import devkor.com.teamcback.domain.place.dto.response.*; import devkor.com.teamcback.domain.place.entity.Place; import devkor.com.teamcback.domain.place.repository.PlaceRepository; import devkor.com.teamcback.domain.routes.entity.Node; import devkor.com.teamcback.domain.routes.repository.NodeRepository; -import devkor.com.teamcback.domain.suggestion.dto.response.SavePlaceImageRes; import devkor.com.teamcback.global.exception.exception.GlobalException; -import devkor.com.teamcback.infra.s3.FilePath; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; import java.util.List; @@ -85,21 +77,6 @@ public DeletePlaceRes deletePlace(Long facilityId) { return new DeletePlaceRes(); } - // 장소 사진 저장 - @Transactional - public SavePlaceImageRes savePlaceImage(Long placeId, List images) { - - Place place = findPlace(placeId); - - if(place.getFileUuid() == null) { - place.setFileUuid(fileUtil.createFileUuid()); - } - - fileUtil.upload(images, place.getFileUuid(), null, FilePath.PLACE); - - return new SavePlaceImageRes(); - } - private Building findBuilding(Long buildingId) { return buildingRepository.findById(buildingId).orElseThrow(() -> new GlobalException(NOT_FOUND_BUILDING)); } diff --git a/src/main/java/devkor/com/teamcback/domain/suggestion/dto/response/SavePlaceImageRes.java b/src/main/java/devkor/com/teamcback/domain/suggestion/dto/response/SavePlaceImageRes.java deleted file mode 100644 index 3000f2ca..00000000 --- a/src/main/java/devkor/com/teamcback/domain/suggestion/dto/response/SavePlaceImageRes.java +++ /dev/null @@ -1,9 +0,0 @@ -package devkor.com.teamcback.domain.suggestion.dto.response; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "건물 사진 저장 완료") -@JsonIgnoreProperties -public class SavePlaceImageRes { -}