-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] poster, notice, casting 이미지 수정, dto로 받은 공연관련 이미지의 imageUrl을 db에 저장안하던 기존 로직 수정 #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,24 +77,32 @@ public AmateurEnrollResponseDTO.AmateurEnrollResult enrollShow(Long memberId, | |
|
|
||
| //posterImageUrl 필드는 이미 Converter에서 기입, 포스터 사진 DB에만 저장(1개만) | ||
| ImageRequestDTO.PosterImageRequestDTO dto = requestDTO.getPosterImageRequestDTO(); | ||
|
|
||
| //poster 이미지는 없으면 에러 | ||
| if(dto.getKeyName() == null || dto.getKeyName().isBlank()){ | ||
| throw new GeneralException(ErrorStatus.INVALID_S3_KEY); | ||
| } | ||
|
|
||
| ImageRequestDTO.FullImageRequestDTO fullImageRequestDTO = ImageRequestDTO.FullImageRequestDTO.builder() | ||
| .keyName(dto.getKeyName()) | ||
| .filePath(FilePath.amateurShow) | ||
| .contentId(newAmateurShow.getId()) | ||
| .memberId(memberId) | ||
| .build(); | ||
| imageService.saveImage(memberId, fullImageRequestDTO); | ||
|
|
||
| // // 좋아요한 멤버리스트 | ||
| // List<MemberLike> memberLikers = memberLikeRepository.findByPerformerId(memberId); | ||
| // // 좋아요한 멤버가 한 명 이상일 때만 | ||
| // if(!memberLikers.isEmpty()) { | ||
| // List<Member> likers = memberLikers.stream() | ||
| // .map(MemberLike::getLiker) | ||
| // .collect(Collectors.toList()); | ||
| // | ||
| // eventPublisher.publishEvent(new NewShowEvent(newAmateurShow.getId(), memberId, likers)); //공연등록 이벤트 생성 | ||
| // } | ||
|
|
||
| imageService.saveImageWithImageUrl(memberId, fullImageRequestDTO, Optional.ofNullable(dto.getImageUrl())); | ||
|
|
||
|
|
||
| // 좋아요한 멤버리스트 | ||
| List<MemberLike> memberLikers = memberLikeRepository.findByPerformerId(memberId); | ||
| // 좋아요한 멤버가 한 명 이상일 때만 | ||
| if(!memberLikers.isEmpty()) { | ||
| List<Member> likers = memberLikers.stream() | ||
| .map(MemberLike::getLiker) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| eventPublisher.publishEvent(new NewShowEvent(newAmateurShow.getId(), memberId, likers)); //공연등록 이벤트 생성 | ||
| } | ||
|
|
||
| // response | ||
| return AmateurConverter.toAmateurEnrollDTO(newAmateurShow); | ||
|
|
@@ -109,13 +117,24 @@ private void saveRelatedEntity(AmateurEnrollRequestDTO requestDTO, AmateurShow a | |
| List<AmateurCasting> amateurCastings = amateurCastingRepository.saveAll(castings); | ||
| // 캐스팅 사진 저장(1개씩) | ||
| amateurCastings.forEach(amateurCasting -> { | ||
| String keyName = amateurCasting.getCastingImageKeyName(); | ||
| // keyName 없으면 스킵 | ||
| if (keyName == null || keyName.isBlank()) { | ||
| return; | ||
| } | ||
|
|
||
| ImageRequestDTO.FullImageRequestDTO fullImageRequestDTO = ImageRequestDTO.FullImageRequestDTO.builder() | ||
| .keyName(amateurCasting.getCastingImageKeyName()) | ||
| .filePath(FilePath.amateurShow) | ||
| .contentId(amateurShow.getId()) | ||
| .filePath(FilePath.casting) | ||
| .contentId(amateurCasting.getId()) | ||
| .memberId(memberId) | ||
| .build(); | ||
| imageService.saveImage(memberId, fullImageRequestDTO); | ||
|
|
||
| imageService.saveImageWithImageUrl( | ||
| memberId, | ||
| fullImageRequestDTO, | ||
| Optional.ofNullable(amateurCasting.getCastingImageUrl()) | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -124,14 +143,22 @@ private void saveRelatedEntity(AmateurEnrollRequestDTO requestDTO, AmateurShow a | |
| if (amateurNotice != null) { | ||
| amateurNoticeRepository.save(amateurNotice); | ||
|
|
||
| ImageRequestDTO.FullImageRequestDTO fullImageRequestDTO = ImageRequestDTO.FullImageRequestDTO.builder() | ||
| .keyName(requestDTO.getNotice().getNoticeImageRequestDTO().getKeyName()) | ||
| .filePath(FilePath.amateurShow) | ||
| .contentId(amateurShow.getId()) | ||
| .memberId(memberId) | ||
| .build(); | ||
| ImageRequestDTO.NoticeImageRequestDTO noticeImageDTO = requestDTO.getNotice().getNoticeImageRequestDTO(); | ||
| //keyName 비었으면 스킵 | ||
| if (noticeImageDTO != null && noticeImageDTO.getKeyName() != null && !noticeImageDTO.getKeyName().isBlank()) { | ||
| ImageRequestDTO.FullImageRequestDTO fullImageRequestDTO = ImageRequestDTO.FullImageRequestDTO.builder() | ||
| .keyName(noticeImageDTO.getKeyName()) | ||
| .filePath(FilePath.notice) | ||
| .contentId(amateurNotice.getId()) | ||
| .memberId(memberId) | ||
| .build(); | ||
|
|
||
| imageService.saveImage(memberId, fullImageRequestDTO); | ||
| imageService.saveImageWithImageUrl( | ||
| memberId, | ||
| fullImageRequestDTO, | ||
| Optional.ofNullable(noticeImageDTO.getImageUrl()) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // 티켓 | ||
|
|
@@ -170,32 +197,17 @@ public AmateurEnrollResponseDTO.AmateurEnrollResult updateShow(Long memberId, Lo | |
| throw new GeneralException(ErrorStatus.MEMBER_NOT_AUTHORIZED); | ||
| } | ||
|
|
||
| //포스터 사진 수정 | ||
| ImageRequestDTO.PosterImageRequestDTO dto = requestDTO.getPosterImageRequestDTO(); | ||
| if (dto != null && dto.getKeyName() != null && !dto.getKeyName().isBlank()) { | ||
| // 현재 포스터 이미지 조회 (show당 1개) | ||
| Image existingImage = imageRepository | ||
| .findAllByFilePathAndContentId(FilePath.amateurShow, amateurShow.getId()) | ||
| .stream() | ||
| .findFirst() | ||
| .orElse(null); | ||
|
|
||
| // 기존 keyName과 다르면 기존 이미지 삭제 후 교체 | ||
| if (existingImage != null && !existingImage.getKeyName().equals(dto.getKeyName())) { | ||
| imageService.deleteImage(existingImage.getId(), memberId); | ||
| } | ||
| imageService.updateShowImage( | ||
| memberId, | ||
| dto.getKeyName(), | ||
| Optional.ofNullable(dto.getImageUrl()), | ||
| amateurShow.getId(), | ||
| FilePath.amateurShow | ||
| ); | ||
|
|
||
| ImageRequestDTO.FullImageRequestDTO fullImageRequestDTO = ImageRequestDTO.FullImageRequestDTO.builder() | ||
| .keyName(dto.getKeyName()) | ||
| .filePath(FilePath.amateurShow) | ||
| .contentId(amateurShow.getId()) | ||
| .memberId(memberId) | ||
| .build(); | ||
|
|
||
| imageService.saveImage(memberId, fullImageRequestDTO); | ||
| //amateurShow 엔티티 내 posterImageUrl 필드 수정 | ||
| amateurShow.updatePosterImageUrl(dto.getImageUrl()); | ||
|
|
||
| } | ||
|
|
||
| // 기본 정보 업데이트 | ||
|
|
@@ -234,6 +246,21 @@ private void updateNotice(AmateurShow amateurShow, AmateurUpdateRequestDTO.Updat | |
| } | ||
| } | ||
| } | ||
|
|
||
| //NoticeImage 수정 | ||
| if (noticeDTO == null) return; | ||
|
|
||
| AmateurNotice notice = amateurShow.getAmateurNotice(); | ||
| ImageRequestDTO.NoticeImageRequestDTO dto = noticeDTO.getNoticeImageRequestDTO(); | ||
| if (notice != null && dto != null && dto.getKeyName() != null && !dto.getKeyName().isBlank()){ | ||
| imageService.updateShowImage( | ||
| amateurShow.getMember().getId(), | ||
| dto.getKeyName(), | ||
| Optional.ofNullable(dto.getImageUrl()), | ||
| notice.getId(), | ||
| FilePath.notice | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| private void updateCasting(AmateurShow show, List<AmateurUpdateRequestDTO.UpdateCasting> dtos) { | ||
|
|
@@ -246,16 +273,35 @@ private void updateCasting(AmateurShow show, List<AmateurUpdateRequestDTO.Update | |
| List<AmateurCasting> updatedList = new ArrayList<>(); | ||
|
|
||
| for (AmateurUpdateRequestDTO.UpdateCasting dto : dtos) { | ||
| Long contentId; | ||
|
|
||
| if (dto.getCastingId() != null && existingMap.containsKey(dto.getCastingId())) { | ||
| // 기존 객체 수정 | ||
| AmateurCasting existing = existingMap.get(dto.getCastingId()); | ||
| existing.update(dto); | ||
| updatedList.add(existing); | ||
| existingMap.remove(dto.getCastingId()); | ||
| contentId = existing.getId(); | ||
| } else { | ||
| // 새 객체 추가 | ||
| AmateurCasting newCasting = AmateurConverter.toSingleCasting(dto, show); | ||
| updatedList.add(newCasting); | ||
| AmateurCasting savedCasting = amateurCastingRepository.save(newCasting); | ||
| updatedList.add(savedCasting); | ||
|
|
||
| contentId = savedCasting.getId(); | ||
| } | ||
|
|
||
|
|
||
| // 캐스팅 이미지 업데이트 | ||
| ImageRequestDTO.CastingImageRequestDTO castingDTO = dto.getCastingImageRequestDTO(); | ||
| if (castingDTO != null && castingDTO.getKeyName() != null && !castingDTO.getKeyName().isBlank()) { | ||
| imageService.updateShowImage( | ||
| show.getMember().getId(), | ||
| castingDTO.getKeyName(), | ||
| Optional.ofNullable(castingDTO.getImageUrl()), | ||
| contentId, | ||
| FilePath.casting | ||
| ); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -267,6 +313,7 @@ private void updateCasting(AmateurShow show, List<AmateurUpdateRequestDTO.Update | |
| // 최종 리스트 갱신 | ||
| show.getAmateurCastingList().clear(); | ||
| show.getAmateurCastingList().addAll(updatedList); | ||
|
|
||
| } | ||
|
|
||
| private void updateStaff(AmateurShow show, List<AmateurUpdateRequestDTO.UpdateStaff> dtos) { | ||
|
|
@@ -363,10 +410,28 @@ public void deleteShow(Long memberId, Long amateurShowId) { | |
| throw new GeneralException(ErrorStatus.MEMBER_NOT_AUTHORIZED); | ||
| } | ||
|
|
||
| amateurShowRepository.delete(amateurShow); | ||
| //포스터 삭제 | ||
| Image posterImg = imageRepository.findByFilePathAndContentId(FilePath.amateurShow, amateurShowId); | ||
| imageService.deleteImage(posterImg.getId(), memberId); | ||
|
Comment on lines
+413
to
+415
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check for poster image before deletion.
//포스터 삭제
Image posterImg = imageRepository.findByFilePathAndContentId(FilePath.amateurShow, amateurShowId);
-imageService.deleteImage(posterImg.getId(), memberId);
+if (posterImg != null) {
+ imageService.deleteImage(posterImg.getId(), memberId);
+}🤖 Prompt for AI Agents |
||
|
|
||
| // 공지 삭제 | ||
| if (amateurShow.getAmateurNotice() != null) { | ||
| Image noticeImg = imageRepository.findByFilePathAndContentId(FilePath.notice, amateurShow.getAmateurNotice().getId()); | ||
| if (noticeImg != null) { | ||
| imageService.deleteImage(noticeImg.getId(), memberId); | ||
| } | ||
| } | ||
|
|
||
| List<Image> images = imageRepository.findAllByFilePathAndContentId(FilePath.amateurShow, amateurShowId); | ||
| images.forEach(image -> imageService.deleteImage(image.getId(), memberId)); | ||
| //캐스팅 삭제 | ||
| List<AmateurCasting> amateurCastings = amateurShow.getAmateurCastingList(); | ||
| List<Image> castingImages = amateurCastings.stream() | ||
| .map(casting -> imageRepository.findByFilePathAndContentId(FilePath.casting, casting.getId())) | ||
| .filter(Objects::nonNull) | ||
| .toList(); | ||
| castingImages.forEach(image -> imageService.deleteImage(image.getId(), memberId)); | ||
|
|
||
| //amateurShow 삭제 | ||
| amateurShowRepository.delete(amateurShow); | ||
| } | ||
|
|
||
| // 소극장 공연 단건 조회 | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,7 +30,7 @@ public class S3Controller { | |||||||||||||||||
| @GetMapping("/uploadUrl") | ||||||||||||||||||
| public ResponseEntity<Map<String, String>> getPresignedPutUrl( | ||||||||||||||||||
| @Parameter(description = "업로드할 이미지 파일의 확장자(jpg, jpeg, png, gif)", required = true) @RequestParam @NotBlank String imageExtension, | ||||||||||||||||||
| @Parameter(description = "업로드할 기능(board, photoAlbum, amateurShow)", required = true) @RequestParam FilePath filePath, | ||||||||||||||||||
| @Parameter(description = "업로드할 사진 종류(board, photoAlbum, amateurShow(포스터), notice, casting)", required = true) @RequestParam FilePath filePath, | ||||||||||||||||||
| @AuthenticationPrincipal(expression = "member") Member member) { | ||||||||||||||||||
|
|
||||||||||||||||||
| return ResponseEntity.ok(s3Service.createPresignedPutUrl(imageExtension, filePath, member.getId())); | ||||||||||||||||||
|
|
@@ -46,7 +46,7 @@ public ResponseEntity<Map<String, String>> getPresignedPutUrl( | |||||||||||||||||
| public ResponseEntity<List<Map<String, String>>> getPresignedPutUrls ( | ||||||||||||||||||
| @io.swagger.v3.oas.annotations.parameters.RequestBody( | ||||||||||||||||||
| description = "각 이미지의 확장자 jpg, jpeg, png, gif ...", required = true) @RequestBody List<@NotBlank String> extensions, | ||||||||||||||||||
| @Parameter(description = "업로드할 기능(board, photoAlbum, amateurShow)", required = true) @RequestParam FilePath filePath, | ||||||||||||||||||
| @Parameter(description = "업로드할 기능(board, photoAlbum)", required = true) @RequestParam FilePath filePath, | ||||||||||||||||||
| @AuthenticationPrincipal(expression = "member") Member member) { | ||||||||||||||||||
|
|
||||||||||||||||||
| return ResponseEntity.ok(s3Service.createPresignedPutUrls(extensions, filePath, member.getId())); | ||||||||||||||||||
|
|
@@ -83,6 +83,9 @@ public void deleteFile(@RequestParam String keyName, | |||||||||||||||||
| @Operation(summary = "keyName에 해당하는 파일이 S3에 실제 존재하는지 확인") | ||||||||||||||||||
| public ResponseEntity<Boolean> doesObjectExist(@RequestParam String keyName, | ||||||||||||||||||
| @AuthenticationPrincipal(expression = "member") Member member){ | ||||||||||||||||||
| if(keyName == null){ | ||||||||||||||||||
| return ResponseEntity.ok(false); | ||||||||||||||||||
| } | ||||||||||||||||||
| return ResponseEntity.ok(s3Service.doesObjectExist(keyName, member.getId())); | ||||||||||||||||||
|
Comment on lines
+86
to
89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- if(keyName == null){
+ if (keyName == null || keyName.isBlank()) {
return ResponseEntity.ok(false);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| package cc.backend.image; | ||
|
|
||
| public enum FilePath { | ||
| board, photoAlbum, amateurShow | ||
| board, photoAlbum, amateurShow, notice, casting | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.stream.Collectors; | ||
| @Slf4j | ||
| @Service | ||
|
|
@@ -40,18 +41,29 @@ public class ImageService { | |
| @Transactional | ||
| public ImageResponseDTO.ImageResultWithPresignedUrlDTO saveImage(Long memberId, ImageRequestDTO.FullImageRequestDTO requestDTO) { | ||
|
|
||
| return saveImageWithImageUrl(memberId, requestDTO, Optional.empty()); | ||
| } | ||
|
|
||
| @Transactional | ||
| public ImageResponseDTO.ImageResultWithPresignedUrlDTO saveImageWithImageUrl( | ||
| Long memberId, | ||
| ImageRequestDTO.FullImageRequestDTO requestDTO, | ||
| Optional<String> imageUrlOpt) { | ||
|
|
||
| memberRepository.findById(memberId).orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); | ||
| //S3에 실제 존재하는 이미지인지 검증 | ||
| if(!s3Service.doesObjectExist(requestDTO.getKeyName(), memberId)) { | ||
| throw new GeneralException(ErrorStatus.NOT_FOUND_IN_S3); | ||
| } | ||
|
|
||
| String imageUrl = imageUrlOpt.orElse(null); | ||
| Image image = Image.builder() | ||
| .keyName(requestDTO.getKeyName()) | ||
| .filePath(requestDTO.getFilePath()) | ||
| .contentId(requestDTO.getContentId()) | ||
| .uploadedAt(LocalDateTime.now()) | ||
| .memberId(memberId) | ||
| .imageUrl(imageUrl) | ||
| .build(); | ||
|
|
||
| Image newImage = imageRepository.save(image); | ||
|
|
@@ -161,4 +173,47 @@ public void deleteImage(Long imageId, Long memberId) { | |
| } | ||
| } | ||
|
|
||
| //amateurShow(poster, notice, casting) 이미지 수정 | ||
| @Transactional | ||
| public void updateShowImage( | ||
| Long memberId, | ||
| String keyName, | ||
| Optional<String> imageUrlOpt, | ||
| Long contentId, | ||
| FilePath filePath) { | ||
| if (keyName == null || keyName.isBlank()) { | ||
| return; // keyName 없으면 처리하지 않음 | ||
| } | ||
|
|
||
| if (!s3Service.doesObjectExist(keyName, memberId)) { | ||
| throw new GeneralException(ErrorStatus.NOT_FOUND_IN_S3); | ||
| } | ||
| String imageUrl = imageUrlOpt.orElse(null); | ||
|
|
||
| // 기존 이미지 조회 (filePath + contentId 기준) | ||
| Image existingImage = imageRepository | ||
| .findByFilePathAndContentId(filePath, contentId); | ||
|
|
||
| // 기존 이미지가 있고, keyName이 다르면 삭제 후 새로 저장 | ||
| if (existingImage != null) { | ||
| // keyName 변경이면 S3/DB 모두 삭제 | ||
| if (!existingImage.getKeyName().equals(keyName)) { | ||
| deleteImage(existingImage.getId(), memberId);} | ||
| else {// 같은 keyName이면 DB row만 교체(중복 방지), S3는 유지 | ||
| imageRepository.delete(existingImage); | ||
| } | ||
| } | ||
|
Comment on lines
+193
to
+205
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Find ImageRepository interface definition
echo "=== Searching for ImageRepository interface ==="
rg -n "interface ImageRepository|findByFilePathAndContentId" --type=java -A 5
# 2) Find Image entity class
echo "=== Searching for Image entity class ==="
rg -n "class Image\b" --type=java -B 3 -A 30 | head -100
# 3) Search for unique constraints or indexes in entity or annotations
echo "=== Searching for unique constraints/indexes ==="
rg -n "@UniqueConstraint|@Index|uniqueConstraints|@Column.*unique" --type=java -B 2 -A 2Repository: SeeATheater/CC_Backend Length of output: 8773 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for migration files or SQL schema definitions
echo "=== Searching for migrations or schema files ==="
fd -e 'sql' -e 'yml' -e 'yaml' | head -20
echo ""
echo "=== Searching for Flyway/Liquibase migrations ==="
find . -type f \( -name "V*.sql" -o -name "*.sql" \) 2>/dev/null | head -20
echo ""
echo "=== Checking for unique constraint in schema files ==="
rg -n "UNIQUE|unique|constraint" -g '*.sql' -C 2 | grep -i "file_path\|content_id\|image" -C 2 || echo "No matches found"Repository: SeeATheater/CC_Backend Length of output: 565 Add unique constraint on
Add 🤖 Prompt for AI Agents |
||
| // 새 이미지 저장 | ||
| Image image = Image.builder() | ||
| .keyName(keyName) | ||
| .imageUrl(imageUrl) | ||
| .filePath(filePath) | ||
| .contentId(contentId) | ||
| .uploadedAt(LocalDateTime.now()) | ||
| .memberId(memberId) | ||
| .build(); | ||
|
|
||
| imageRepository.save(image); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Poster update via
updateShowImageis cleaner than delete+insert; watch for nullimageUrl.If FE ever sends
keyNamebut omitsimageUrl, you’ll setamateurShow.posterImageUrlto null (Line 210-211). If poster URL is required for reads, consider requiringimageUrlwhenkeyNameis provided (or derive URL server-side).🤖 Prompt for AI Agents