Skip to content

Commit

Permalink
와인 이미지 업로드 후 CSV에 url 저장 (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeongyeon0208 authored Jan 10, 2025
2 parents a960a90 + 4f9bd6e commit 633100b
Show file tree
Hide file tree
Showing 19 changed files with 454 additions and 120 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" // 어노테이션 프로세서 설정
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // 어노테이션 프로세서 설정
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // 어노테이션 프로세서 설정

implementation 'com.opencsv:opencsv:5.7.1' // OpenCSV 의존성 추가
}

// gradle clean 시에 QClass 디렉토리 삭제
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

import static jakarta.persistence.FetchType.*;

@Entity
Expand All @@ -29,26 +27,22 @@ public class TastingNote extends BaseEntity {
private Member member;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "wine_id")
@JoinColumn(name = "wine_id", nullable = false)
private Wine wine;

private String color;

// 시음 날짜
private LocalDate tasteDate;

// 점수 0 ~ 5
private int sugarContent;
private int acidity;
private int tannin;
private int body;
private int alcohol;

// nose를 TastingNote와 OneToMany 관계로 설정
@OneToMany(mappedBy = "tastingNote", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TastingNoteNose> noseList = new ArrayList<>();

// 만족도 0 ~ 5, 소수점 가능
private float rating;

private String review;
Expand Down Expand Up @@ -87,9 +81,8 @@ public static TastingNote create(Member member, Wine wine, TastingNoteRequest ta
.review(tastingNoteRequest.getReview())
.build();

Set<String> uniqueNoseElements = new HashSet<>(tastingNoteRequest.getNose());
Set<String> uniqueNoseElements = new LinkedHashSet<>(tastingNoteRequest.getNose());
uniqueNoseElements.forEach(tastingNote::addNoseElement);

return tastingNote;
}

Expand All @@ -100,13 +93,12 @@ public TastingNote addNoseElement(String noseElement) {
return this;
}

public TastingNote removeNose(TastingNoteNose nose) {
this.noseList.remove(nose);
nose.updateTastingNote(null);
public TastingNote removeTastingNoteNose(TastingNoteNose tastingNoteNose) {
this.noseList.remove(tastingNoteNose);
tastingNoteNose.updateTastingNote(null);
return this;
}


public void updateTastingNote(String color, LocalDate tasteDate,
Integer sugarContent, Integer acidity, Integer tannin, Integer body, Integer alcohol,
List<String> updateNoseList, Float rating, String review){
Expand All @@ -117,16 +109,19 @@ public void updateTastingNote(String color, LocalDate tasteDate,
if (tannin != null) this.tannin = tannin;
if (body != null) this.body = body;
if (alcohol != null) this.alcohol = alcohol;
if (updateNoseList != null) this.updateTastingNoteNoseList(updateNoseList);
if (rating != null) this.rating = rating;
if (review != null) this.review = review;
if (updateNoseList != null) this.updateTastingNoteNoseList(updateNoseList);
}

public void updateTastingNoteNoseList(List<String> updateNoseList) {
// 기존 noseList 전체 삭제
noseList.forEach(this::removeNose);
// updateNoseList로 대체
Set<String> uniqueNoseElements = new HashSet<>(updateNoseList);

this.noseList.removeIf(nose->{
nose.updateTastingNote(null);
return true;
});

Set<String> uniqueNoseElements = new LinkedHashSet<>(updateNoseList);
uniqueNoseElements.forEach(this::addNoseElement);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(
uniqueConstraints = @UniqueConstraint(columnNames = {"tasting_note_id", "nose_element"})
)
public class TastingNoteNose {

@Id
Expand All @@ -20,7 +17,6 @@ public class TastingNoteNose {
@JoinColumn(name = "tasting_note_id", nullable = false)
private TastingNote tastingNote;

// 향 요소
@Column(nullable = false)
private String noseElement;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.drinkeg.drinkeg.domain.tastingNote.dto.request;

import jakarta.validation.constraints.*;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDate;
Expand Down Expand Up @@ -33,11 +34,25 @@ public class TastingNoteUpdateRequest {
@Max(value = 100, message = "알콜도는 0 이상 100 이하의 정수 값이어야 합니다.")
private Integer alcohol;

private final List<String> updateNoseList = new ArrayList<>();
private List<String> updateNoseList;

@Min(0)
@Max(5)
private Float rating;

private String review;

@Builder
public TastingNoteUpdateRequest(String color, LocalDate tastingDate, Integer sugarContent, Integer acidity, Integer tannin, Integer body, Integer alcohol, List<String> updateNoseList, Float rating, String review) {
this.color = color;
this.tastingDate = tastingDate;
this.sugarContent = sugarContent;
this.acidity = acidity;
this.tannin = tannin;
this.body = body;
this.alcohol = alcohol;
this.updateNoseList = updateNoseList;
this.rating = rating;
this.review = review;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

import com.drinkeg.drinkeg.domain.tastingNote.domain.TastingNote;
import feign.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.*;

import java.util.List;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface TastingNoteRepository extends JpaRepository<TastingNote, Long>, TastingNoteRepositoryCustom {

@Query("SELECT t FROM TastingNote t WHERE t.wine.id = :wineId ORDER BY t.updatedAt DESC LIMIT 3")
List<TastingNote> findRecentThreeTastingNoteBy(Long wineId);

@Modifying
@Query("UPDATE TastingNote t SET t.member = null WHERE t.member.username = :username")
void updateTastingNoteMemberNull(@Param("username") String username);

@Query("SELECT t FROM TastingNote t WHERE t.wine.id = :wineId ORDER BY t.updatedAt DESC LIMIT 3")
List<TastingNote> findRecentThreeTastingNoteBy(Long wineId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,14 @@ public class TastingNoteServiceImpl implements TastingNoteService {
@Override
public Long saveTastingNote(TastingNoteRequest tastingNoteRequest, String username) {

// 회원을 조회한다.
Member member = memberRepository.findByUsername(username).orElseThrow(
() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)
);

// 와인을 찾는다.
Long wineId = tastingNoteRequest.getWineId();
Wine wine = wineRepository.findById(wineId).orElseThrow(
() -> new GeneralException(ErrorStatus.WINE_NOT_FOUND));

// TastingNote를 저장한다.
TastingNote save = tastingNoteRepository.save(TastingNote.create(member, wine, tastingNoteRequest));

eventPublisher.publishEvent(new TastingNoteUpdateEvent(wineId));
Expand All @@ -60,9 +57,8 @@ public Long saveTastingNote(TastingNoteRequest tastingNoteRequest, String userna

@Override
public TastingNoteResponse showTastingNoteByIdAndUsername(Long noteId, String username) {
TastingNote tastingNote = tastingNoteRepository.findById(noteId).orElseThrow(()
-> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
);
TastingNote tastingNote = tastingNoteRepository.findById(noteId).orElseThrow(
() -> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND));

if(!tastingNote.getMember().getUsername().equals(username))
throw new GeneralException(ErrorStatus.TASTING_NOTE_FORBIDDEN);
Expand All @@ -82,75 +78,44 @@ public AllTastingNoteResponse findAllTastingNote(TastingNoteWineSort wineSort, S
return AllTastingNoteResponse.create(tastingNoteSortCountResponse, tastingNotePreviewResponseList);
}

// 와인 타입별 필터링 로직
private boolean filterBySort(TastingNote note, String sort) {
String wineSort = note.getWine().getSort();

switch (sort) {
case "red":
return wineSort.contains("레드");
case "white":
return wineSort.contains("화이트");
case "sparkling":
return wineSort.contains("스파클링");
case "rose":
return wineSort.contains("로제");
case "all":
return true; // 전체 보기
default:
return !wineSort.contains("레드") && !wineSort.contains("화이트")
&& !wineSort.contains("스파클링") && !wineSort.contains("로제");
}
}

@Override
public void updateTastingNote(Long noteId, TastingNoteUpdateRequest t, String username) {

// 회원을 조회한다.
Member member = memberRepository.findByUsername(username).orElseThrow(
() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)
);

// noteId로 TastingNote를 찾는다.
TastingNote foundNote = tastingNoteRepository.findById(noteId).orElseThrow(()
-> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
);

// TastingNote의 Member가 요청한 Member와 같은지 확인한다.
if(!foundNote.getMember().equals(member)) {
throw new GeneralException(ErrorStatus.TASTING_NOTE_FORBIDDEN);
}

// TastingNote를 업데이트한다.
foundNote.updateTastingNote(t.getColor(), t.getTastingDate(),
t.getSugarContent(), t.getAcidity(), t.getTannin(), t.getBody(), t.getAlcohol(),
t.getUpdateNoseList(), t.getRating(), t.getReview());

tastingNoteRepository.save(foundNote);

}

@Override
public Long deleteTastingNote(Long noteId, String username) {

// 회원을 조회한다.
Member member = memberRepository.findByUsername(username).orElseThrow(
() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)
);

// noteId로 TastingNote 를 찾는다.
TastingNote foundNote = tastingNoteRepository.findById(noteId).orElseThrow(
() -> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
);

// TastingNote 의 Member 가 요청한 Member 와 같은지 확인한다.
if(!foundNote.getMember().equals(member)) {
Member foundNoteMember = foundNote.getMember();

if(foundNoteMember == null || !foundNoteMember.equals(member)) {
throw new GeneralException(ErrorStatus.TASTING_NOTE_FORBIDDEN);
}

Long wineId = foundNote.getWine().getId();

// TastingNote를 삭제한다.
tastingNoteRepository.delete(foundNote);

eventPublisher.publishEvent(new TastingNoteUpdateEvent(wineId));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.drinkeg.drinkeg.domain.wine.controller;

import com.drinkeg.drinkeg.domain.wine.service.AdminWineService;
import com.drinkeg.drinkeg.global.apipayLoad.ApiResponse;
import com.opencsv.exceptions.CsvException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@Tag(name = "Wine Admin", description = "와인 관리자 API")
@RestController
@RequiredArgsConstructor
public class AdminWineController {
private final AdminWineService adminWineService;

// 와인 이미지 업로드
@PostMapping("/admin/wine/img/db")
@Operation(
summary = "와인 이미지 업로드 후 데이터베이스에 저장",
description = "와인 이미지 파일은 있지만 데이터베이스에는 없는 와인에 대해 업데이트 하기 위한 API"
)
public ApiResponse<?> uploadWineImageDB() {
try {
adminWineService.uploadWineImage();
} catch (IOException e) {
throw new RuntimeException(e);
}
return ApiResponse.onSuccess("업로드 후 DB저장 성공");
}

// 와인 이미지 업로드
@PostMapping("/admin/wine/img/csv")
@Operation(
summary = "와인 이미지 업로드 후 csv에 url 저장",
description = "csv에서 받아온 와인들에 대해 와인 이미지 업로드 후 csv에 url 저장"
)
public ApiResponse<?> uploadWineImageCSV() {
try {
adminWineService.uploadWineImageCSV();
} catch (IOException | CsvException e) {
throw new RuntimeException(e);
}
return ApiResponse.onSuccess("업로드 후 csv저장 성공");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.drinkeg.drinkeg.global.apipayLoad.ApiResponse;
import com.drinkeg.drinkeg.domain.wine.service.WineService;
import com.drinkeg.drinkeg.domain.member.dto.loginDTO.commonDTO.PrincipalDetail;
import com.opencsv.exceptions.CsvException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -76,16 +77,4 @@ public ApiResponse<List<HomeWineResponse>> mostLikedWine(HttpServletResponse res
response.setHeader("Cache-Control", "max-age=3600, public");
return ApiResponse.onSuccess(mostLikedWineList);
}

// 와인 이미지 업로드
@PostMapping("/upload")
@Operation(summary = "와인 이미지 업로드", description = "백엔드에세 와인 이미지 업로드 하기 위한 API")
public ApiResponse<?> uploadWineImage() {
try {
wineService.uploadWineImage();
} catch (IOException e) {
throw new RuntimeException(e);
}
return ApiResponse.onSuccess("업로드 성공");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.drinkeg.drinkeg.domain.wine.dto.response;

import com.drinkeg.drinkeg.domain.wine.domain.Wine;
import com.querydsl.core.annotations.QueryProjection;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.AccessLevel;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import lombok.*;

@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class WinePreviewResponse {

Expand Down
Empty file.
Loading

0 comments on commit 633100b

Please sign in to comment.