-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat(#92): 이미지 삭제/조회 api 개발 및 리뷰 수정 로직 변경 #93
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 |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package everymeal.server.admin.controller; | ||
|
||
|
||
import everymeal.server.admin.dto.AdminUserDto.DefaultProfileImageRes; | ||
import everymeal.server.admin.service.AdminUserService; | ||
import everymeal.server.global.dto.response.ApplicationResponse; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import java.util.List; | ||
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 | ||
@RequestMapping("/api/v1/admin/users") | ||
@RequiredArgsConstructor | ||
@Tag(name = "Admin API", description = "어드민에서 관리되는 데이터 관련 API입니다.") | ||
public class AdminController { | ||
|
||
private final AdminUserService adminUserService; | ||
|
||
@Operation( | ||
summary = "유저의 기본 프로필 이미지 정보를 반환합니다.", | ||
description = | ||
"유저의 기본 프로필 이미지 정보를 반환합니다. <br/>" | ||
+ "피그마에 노출되는 순서대로 반환합니다. <br/>" | ||
+ "기본 이미지를 선택 시, imgUrl에 imageKey를 넣어주세요. <br/>") | ||
@GetMapping("/default-profile-images") | ||
public ApplicationResponse<List<DefaultProfileImageRes>> getDefaultProfileImages() { | ||
return ApplicationResponse.ok(adminUserService.getDefaultProfileImages()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package everymeal.server.admin.dto; | ||
|
||
import static everymeal.server.global.util.aws.S3Util.getImgUrl; | ||
|
||
import everymeal.server.admin.entity.UserDefaultProfileImage; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
public class AdminUserDto { | ||
|
||
@Schema(description = "어드민에서 관리되는 유저의 기본 프로필 이미지 정보를 담은 응답 DTO입니다.") | ||
public record DefaultProfileImageRes( | ||
@Schema(description = "유저의 기본 프로필 이미지의 idx입니다.") Long idx, | ||
@Schema(description = "유저의 기본 프로필 이미지의 URL입니다.") String profileImageUrl, | ||
@Schema(description = "유저의 기본 프로필 이미지의 imageKey입니다.") String imageKey) { | ||
public static DefaultProfileImageRes of(UserDefaultProfileImage entity) { | ||
return new DefaultProfileImageRes( | ||
entity.getIdx(), | ||
getImgUrl(entity.getProfileImgUrl()), | ||
entity.getProfileImgUrl()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package everymeal.server.admin.entity; | ||
|
||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import java.time.LocalDateTime; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import org.springframework.data.annotation.CreatedDate; | ||
|
||
@Getter | ||
@Table(catalog = "admin", name = "user_default_profile_image") | ||
@Entity | ||
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) | ||
public class UserDefaultProfileImage { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long idx; | ||
|
||
private String profileImgUrl; | ||
|
||
@CreatedDate | ||
@Column(updatable = false) | ||
private LocalDateTime createdAt; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package everymeal.server.admin.repository; | ||
|
||
|
||
import everymeal.server.admin.entity.UserDefaultProfileImage; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface UserDefaultProfileImageRepository | ||
extends JpaRepository<UserDefaultProfileImage, Long> {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package everymeal.server.admin.service; | ||
|
||
|
||
import everymeal.server.admin.dto.AdminUserDto.DefaultProfileImageRes; | ||
import everymeal.server.admin.repository.UserDefaultProfileImageRepository; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Transactional(readOnly = true) | ||
@Service | ||
@RequiredArgsConstructor | ||
public class AdminUserService { | ||
|
||
private final UserDefaultProfileImageRepository userDefaultProfileImageRepository; | ||
|
||
public List<DefaultProfileImageRes> getDefaultProfileImages() { | ||
return userDefaultProfileImageRepository.findAll().stream() | ||
.map(DefaultProfileImageRes::of) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,16 +7,19 @@ | |
import com.amazonaws.regions.Regions; | ||
import com.amazonaws.services.s3.AmazonS3; | ||
import com.amazonaws.services.s3.AmazonS3ClientBuilder; | ||
import com.amazonaws.services.s3.model.DeleteObjectRequest; | ||
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; | ||
import java.io.File; | ||
import java.net.URL; | ||
import java.time.Instant; | ||
import java.time.temporal.ChronoUnit; | ||
import java.util.Date; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@Slf4j | ||
public class S3Util { | ||
|
||
public static AmazonS3 amazonS3; | ||
|
@@ -56,4 +59,14 @@ public static String getImgUrl(String fileName) { | |
URL url = amazonS3.getUrl(bucket, runningName + File.separator + fileName); | ||
return url.toString(); | ||
} | ||
|
||
public void deleteImage(String fileUrl) { | ||
try { | ||
String fileKey = "dev/" + fileUrl; | ||
amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileKey)); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
log.error("S3 이미지 삭제 실패 fileUrl: {}", fileUrl); | ||
} | ||
Comment on lines
+65
to
+70
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. @Qbeom0925 S3에서 이미지 삭제 실패 시, 이유를 트랙킹하기 위해 로그를 남깁니다. 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. 확실히 실패에 대한 트래킹 목적의 로그를 남기는 것은 좋은 것 같습니다. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
import jakarta.annotation.Nullable; | ||
import jakarta.validation.constraints.Max; | ||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.NotNull; | ||
import java.util.List; | ||
|
||
public record ReviewCreateReq( | ||
|
@@ -14,7 +15,7 @@ public record ReviewCreateReq( | |
description = | ||
"학식에 대한 리뷰 평가 점수를 정수형 최소 1 ~ 최대 5점까지로 입력해주세요. 사진 리뷰인 경우 0으로 보내주세요.", | ||
defaultValue = "5") | ||
@Nullable | ||
@NotNull | ||
Integer grade, | ||
@Schema( | ||
description = "학식에 대한 리뷰 내용을 1글자 이상 입력해주세요. 사진 리뷰인 경우 null로 보내주세요.", | ||
Comment on lines
+18
to
21
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. nullable이면 NPE 발생해서 notNull로 수정했습니다. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,4 +35,8 @@ public Image(String imageUrl, Review review) { | |
this.isDeleted = false; | ||
this.review = review; | ||
} | ||
|
||
public void deleteImage() { | ||
this.isDeleted = true; | ||
} | ||
Comment on lines
+38
to
+41
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. 이미지 객체에서 삭제 여부를 업데이트하는 로직이 ... 없더라구요?!@ 그래서 추가했습니다. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package everymeal.server.review.service; | ||
|
||
|
||
import everymeal.server.review.entity.Image; | ||
import everymeal.server.review.repository.ImageRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ImageCommServiceImpl { | ||
|
||
private final ImageRepository imageRepository; | ||
|
||
@Transactional | ||
public void deleteImage(Image alreadyImg) { | ||
imageRepository.delete(alreadyImg); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,11 @@ | |
import static everymeal.server.global.exception.ExceptionList.REVIEW_MARK_NOT_FOUND; | ||
import static everymeal.server.global.exception.ExceptionList.REVIEW_UNAUTHORIZED; | ||
import static everymeal.server.global.exception.ExceptionList.STORE_NOT_FOUND; | ||
import static everymeal.server.global.util.aws.S3Util.getImgUrl; | ||
|
||
import everymeal.server.global.exception.ApplicationException; | ||
import everymeal.server.global.util.TimeFormatUtil; | ||
import everymeal.server.global.util.aws.S3Util; | ||
import everymeal.server.meal.entity.Restaurant; | ||
import everymeal.server.meal.service.RestaurantCommServiceImpl; | ||
import everymeal.server.review.dto.request.ReviewCreateReq; | ||
|
@@ -36,6 +38,8 @@ public class ReviewServiceImpl implements ReviewService { | |
private final UserCommServiceImpl userCommServiceImpl; | ||
private final ReviewCommServiceImpl reviewCommServiceImpl; | ||
private final StoreRepository storeRepository; | ||
private final S3Util s3Util; | ||
private final ImageCommServiceImpl imageCommServiceImpl; | ||
|
||
@Override | ||
@Transactional | ||
|
@@ -80,6 +84,17 @@ public Long updateReview(ReviewCreateReq request, Long userIdx, Long reviewIdx) | |
Review review = reviewCommServiceImpl.getReviewEntity(reviewIdx); | ||
|
||
// (2) 이미지 주소 <> 이미지 객체 치환 | ||
List<Image> alreadyImageList = review.getImages(); | ||
if (!alreadyImageList.isEmpty()) { // [1,2,3] <> [1,2,4] 3을 삭제 | ||
List<String> reqImgList = request.imageList(); | ||
for (Image alreadyImg : alreadyImageList) { | ||
if (!reqImgList.contains(alreadyImg.getImageUrl())) { | ||
s3Util.deleteImage(alreadyImg.getImageUrl()); | ||
imageCommServiceImpl.deleteImage(alreadyImg); | ||
} | ||
} | ||
} | ||
|
||
Comment on lines
+87
to
+97
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. 원본 이미지와 수정하는 이미지를 비교해서, 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. 데이터 일관성까지 고려해서 만드신것이 너무 멋집니다!! |
||
List<Image> imageList = getImageFromString(request.imageList()); | ||
User user = userCommServiceImpl.getUserEntity(userIdx); | ||
if (review.getUser() != user) { | ||
|
@@ -101,6 +116,7 @@ public Boolean deleteReview(Long userIdx, Long reviewIdx) { | |
Review review = reviewCommServiceImpl.getReviewEntity(reviewIdx, user); | ||
// (2) 기존 데이터 삭제 | ||
review.getRestaurant().removeGrade(review.getGrade()); | ||
review.getImages().forEach(Image::deleteImage); | ||
review.deleteEntity(); | ||
|
||
reviewCommServiceImpl | ||
|
@@ -125,7 +141,7 @@ public ReviewGetRes getReviewWithNoOffSetPaging(ReviewDto.ReviewQueryParam query | |
vo.getIdx(), | ||
vo.getRestaurant().getName(), | ||
vo.getUser().getNickname(), | ||
vo.getUser().getProfileImgUrl(), | ||
getImgUrl(vo.getUser().getProfileImgUrl()), | ||
vo.isTodayReview(), | ||
vo.getGrade(), | ||
vo.getContent(), | ||
|
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.
@Qbeom0925 해당 데이터는 삭제여부로 관리하지 않고, 즉시 삭제를 하는 것이 추후 관리 포인트를 늘리지 않는 것이라 판단하여 meta 데이터로 생성일시만을 두었습니다.