Skip to content

Commit

Permalink
Merge pull request #93 from everymeals/feature/user-profile
Browse files Browse the repository at this point in the history
feat(#92): 이미지 삭제/조회 api 개발 및 리뷰 수정 로직 변경
  • Loading branch information
dldmsql committed Feb 11, 2024
2 parents 0f20330 + cdd9f52 commit 64419e8
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 14 deletions.
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ sonar {
property 'sonar.language', 'java'
property 'sonar.sourceEncoding', 'UTF-8'
property("sonar.test.inclusions", "**/*Test.java")
property "sonar.exclusions", "**/test/**, **/*Application*.java, **/dto/**, **/entity/**, **/*Exception*.java, **/*RepositoryImpl.java, **/global/**, **/resources/**, **/*Dao*.java, **/dev/**"
property "sonar.exclusions", "**/test/**, **/*Application*.java, **/dto/**, **/entity/**, **/*Exception*.java, **/*RepositoryImpl.java, **/global/**, **/resources/**, **/*Dao*.java, **/dev/**, **/admin/**, **/Image*.java"
property "sonar.java.coveragePlugin", "jacoco"
property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/jacoco/test/jacocoTestReport.xml'
}
Expand Down Expand Up @@ -127,6 +127,8 @@ jacocoTestReport {
"**/global/*",
"**/*Dao*",
"**/dev/**",
"**/admin/**",
"**/Image*"
])
}))
}
Expand Down Expand Up @@ -162,6 +164,8 @@ jacocoTestCoverageVerification {
"**/global/*",
"**/*Dao*",
"**/dev/**",
"**/admin/**",
"**/Image*"
])
}))
}
Expand Down
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());
}
}
22 changes: 22 additions & 0 deletions src/main/java/everymeal/server/admin/dto/AdminUserDto.java
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> {}
23 changes: 23 additions & 0 deletions src/main/java/everymeal/server/admin/service/AdminUserService.java
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();
}
}
13 changes: 13 additions & 0 deletions src/main/java/everymeal/server/global/util/aws/S3Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.net.URL;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down Expand Up @@ -50,4 +51,23 @@ public ApplicationResponse<S3GetResignedUrlRes> getPresignedUrl(
return ApplicationResponse.ok(
S3GetResignedUrlRes.builder().imageKey(fileName).url(test.toString()).build());
}

@Operation(
summary = "S3 이미지 삭제",
description =
"""
S3에 저장된 이미지를 삭제합니다.
언제 사용하는 API인가요?
1. 리뷰 작성 중 이미지를 업로드하고, 리뷰 작성을 취소할 때
2. 리뷰 수정 중 이미지를 업로드하고, 리뷰 수정을 취소할 때
3. 리뷰 삭제 시 이미지를 삭제할 때
4. 이미지를 잘못 업로드 했을 때
5. 유저 프로필 이미지를 변경할 때 ( 기본 이미지 제외 )
""")
@DeleteMapping("/image")
public ApplicationResponse<Void> deleteImage(
@RequestParam(value = "fileName") String fileName) {
s3Util.deleteImage(fileName);
return ApplicationResponse.ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -14,7 +15,7 @@ public record ReviewCreateReq(
description =
"학식에 대한 리뷰 평가 점수를 정수형 최소 1 ~ 최대 5점까지로 입력해주세요. 사진 리뷰인 경우 0으로 보내주세요.",
defaultValue = "5")
@Nullable
@NotNull
Integer grade,
@Schema(
description = "학식에 대한 리뷰 내용을 1글자 이상 입력해주세요. 사진 리뷰인 경우 null로 보내주세요.",
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/everymeal/server/review/entity/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ public Image(String imageUrl, Review review) {
this.isDeleted = false;
this.review = review;
}

public void deleteImage() {
this.isDeleted = true;
}
}
5 changes: 2 additions & 3 deletions src/main/java/everymeal/server/review/entity/Review.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,8 @@ public Review(
public void updateEntity(String content, int grade, List<Image> images, Boolean todayReview) {
this.content = content;
this.grade = grade;
if (images != null) {
this.images.clear();
}
// 이미지 연관 리스트 지우고 새로운 이미지 리스트로 교체
this.images.clear();
this.images = images;
this.isTodayReview = todayReview;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@ public ReviewPagingVOWithCnt getReview(ReviewDto.ReviewQueryParam queryParam) {
jpaQueryFactory
.select(review)
.from(review)
.leftJoin(review.images, image)
.leftJoin(review.reviewMarks, reviewMark)
.leftJoin(review.restaurant)
.on(restaurant.idx.eq(queryParam.restaurantIdx()))
.leftJoin(image)
.on(review.idx.eq(image.review.idx).and(image.isDeleted.eq(Boolean.FALSE)))
.leftJoin(reviewMark)
.on(review.idx.eq(reviewMark.review.idx))
.innerJoin(restaurant)
.on(
review.restaurant
.idx
.eq(restaurant.idx)
.and(restaurant.idx.eq(queryParam.restaurantIdx())))
.where(
gtReviewIdx(queryParam.cursorIdx()),
isDeleted(),
Expand All @@ -51,10 +57,22 @@ public ReviewPagingVOWithCnt getReview(ReviewDto.ReviewQueryParam queryParam) {
jpaQueryFactory
.select(review.idx.count())
.from(review)
.leftJoin(review.images, image)
.leftJoin(review.reviewMarks, reviewMark)
.leftJoin(review.restaurant)
.on(restaurant.idx.eq(queryParam.restaurantIdx()))
.leftJoin(image)
.on(
review.idx
.eq(image.review.idx)
.and(image.isDeleted.eq(Boolean.FALSE)))
.leftJoin(reviewMark)
.on(review.idx.eq(reviewMark.review.idx))
.innerJoin(restaurant)
.on(
review.restaurant
.idx
.eq(restaurant.idx)
.and(
restaurant.idx.eq(
queryParam
.restaurantIdx())))
.where(isDeleted(), eqToday(queryParam.filter()))
.fetchOne())
.intValue();
Expand Down
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
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}

List<Image> imageList = getImageFromString(request.imageList());
User user = userCommServiceImpl.getUserEntity(userIdx);
if (review.getUser() != user) {
Expand All @@ -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
Expand All @@ -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(),
Expand Down

0 comments on commit 64419e8

Please sign in to comment.