Skip to content

Commit 8683098

Browse files
committed
[feat] 레시피 삭제 로직 리팩터링 및 MemberRecipe 기반 삭제 API 추가
1 parent 4ee5e70 commit 8683098

7 files changed

Lines changed: 93 additions & 27 deletions

File tree

yechef/src/main/java/sejong/capston/yechef/domain/MemberRecipe/controller/MemberRecipeController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.List;
55
import lombok.RequiredArgsConstructor;
66
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.DeleteMapping;
78
import org.springframework.web.bind.annotation.GetMapping;
89
import org.springframework.web.bind.annotation.PatchMapping;
910
import org.springframework.web.bind.annotation.PathVariable;
@@ -52,6 +53,15 @@ public ResponseEntity<RecipeDto> getRecipeFromMemberRecipe(
5253
return ResponseEntity.ok(recipeService.getRecipeFromMemberRecipe(memberRecipeId));
5354
}
5455

56+
@DeleteMapping("/members/{memberId}/member-recipes/{memberRecipeId}")
57+
public ResponseEntity<Void> deleteMemberRecipe(
58+
@PathVariable("memberId") Long memberId,
59+
@PathVariable("memberRecipeId") Long memberRecipeId
60+
) {
61+
recipeService.deleteMemberRecipe(memberId, memberRecipeId);
62+
return ResponseEntity.noContent().build();
63+
}
64+
5565
}
5666

5767

yechef/src/main/java/sejong/capston/yechef/domain/MemberRecipe/repository/MemberRecipeRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ public interface MemberRecipeRepository extends JpaRepository<MemberRecipe, Long
1515
@Query("SELECT mr.recipe FROM MemberRecipe mr WHERE mr.member.id = :memberId AND mr.isLiked = true")
1616
List<Recipe> findLikedRecipesByMemberId(@Param("memberId") Long memberId);
1717

18+
long countByRecipeId(Long recipeId);
1819
}
1920

yechef/src/main/java/sejong/capston/yechef/domain/Recipe/controller/RecipeController.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class RecipeController {
3232
@PostMapping(value = "/{memberId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
3333
public ResponseEntity<RecipeDto> createRecipe(
3434
@Parameter(description = "회원 ID", required = true)
35-
@PathVariable Long memberId,
35+
@PathVariable("memberId") Long memberId,
3636

3737
@Parameter(description = "GPT 파싱 결과 JSON (문자열 형태)", required = true)
3838
@RequestPart("dto") String dtoText, // String으로 먼저 받음
@@ -59,23 +59,21 @@ public ResponseEntity<RecipeDto> createRecipe(
5959
@GetMapping("/{recipeId}")
6060
public ResponseEntity<RecipeDto> getRecipe(
6161
@Parameter(description = "조회할 레시피 ID", required = true)
62-
@PathVariable Long recipeId
62+
@PathVariable("recipeId") Long recipeId
6363
) {
6464
return ResponseEntity.ok(recipeService.getRecipe(recipeId));
6565
}
6666

67-
@DeleteMapping("/{recipeId}")
67+
@DeleteMapping("/members/{memberId}/recipes/{recipeId}")
6868
public ResponseEntity<Void> deleteRecipe(
69-
@Parameter(description = "사용자 ID (테스트용)", required = true)
70-
@RequestParam Long memberId,
71-
72-
@Parameter(description = "삭제할 레시피 ID", required = true)
73-
@PathVariable Long recipeId
69+
@PathVariable("memberId") Long memberId,
70+
@PathVariable("recipeId") Long recipeId
7471
) {
7572
recipeService.delete(memberId, recipeId);
7673
return ResponseEntity.noContent().build();
7774
}
78-
75+
76+
7977
@GetMapping("/public")
8078
public ResponseEntity<List<Recipe>> getPublicRecipes() {
8179
return ResponseEntity.ok(recipeService.getPublicRecipes());

yechef/src/main/java/sejong/capston/yechef/domain/Recipe/controller/RecipeProgressController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public class RecipeProgressController {
2020

2121
@PostMapping("/{recipeId}/step/{stepNumber}/progress")
2222
public RecipeStepDto progressStep(
23-
@PathVariable Long memberId,
24-
@PathVariable Long recipeId,
25-
@PathVariable int stepNumber,
23+
@PathVariable("memberId") Long memberId,
24+
@PathVariable("recipeId") Long recipeId,
25+
@PathVariable("stepNumber") int stepNumber,
2626
@RequestBody VoiceInputDto input
2727
) {
2828
return progressService.processStep(memberId, recipeId, stepNumber, input.getText());

yechef/src/main/java/sejong/capston/yechef/domain/Recipe/service/RecipeService.java

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ public RecipeDto create(Long memberId, RecipeParseResultDto recipeDto, Multipart
6969

7070
// MemberRecipe 연결
7171
MemberRecipe memberRecipe = MemberRecipe.of(member, recipe);
72-
memberRecipeRepository.save(memberRecipe);
7372

7473
// Member 연결
7574
memberRecipeRepository.save(memberRecipe);
@@ -99,27 +98,40 @@ public RecipeDto getRecipe(Long recipeId) {
9998

10099
@Transactional
101100
public void delete(Long memberId, Long recipeId) {
101+
// 1. 현재 사용자의 MemberRecipe 조회
102102
MemberRecipe memberRecipe = memberRecipeRepository.findByMemberIdAndRecipeId(memberId, recipeId)
103103
.orElseThrow(() -> BaseException.from(ErrorCode.NOT_RECIPE_OWNER));
104104

105105
Recipe recipe = memberRecipe.getRecipe();
106106

107-
// 1. 썸네일 이미지 삭제
108-
if (recipe.getThumbnailImage() != null) {
109-
String thumbnailKey = recipe.getThumbnailImage().getS3Key();
110-
if (thumbnailKey != null) {
111-
s3UploadService.deleteFile(thumbnailKey);
107+
// 2. MemberRecipe 삭제
108+
memberRecipeRepository.delete(memberRecipe);
109+
110+
// 3. 해당 recipe가 다른 멤버에게 공유되지 않았다면 실제로 삭제
111+
long remainingLinks = memberRecipeRepository.countByRecipeId(recipeId);
112+
if (remainingLinks == 0) {
113+
// 3-1. 썸네일 S3 삭제
114+
if (recipe.getThumbnailImage() != null) {
115+
String thumbnailKey = recipe.getThumbnailImage().getS3Key();
116+
if (thumbnailKey != null) {
117+
s3UploadService.deleteFile(thumbnailKey);
118+
}
112119
}
120+
121+
// 3-2. 원본 이미지 S3 삭제
122+
if (recipe.getSourceImage() != null) {
123+
String sourceKey = recipe.getSourceImage().getS3Key();
124+
if (sourceKey != null) {
125+
s3UploadService.deleteFile(sourceKey);
126+
}
127+
}
128+
129+
// 3-3. 레시피 삭제 (Image는 cascade + orphanRemoval)
130+
recipeRepository.delete(recipe);
113131
}
114-
115-
// 2. 사용자 원본 이미지 삭제
116-
if (recipe.getSourceImage() != null) {
117-
s3UploadService.deleteFile(recipe.getSourceImage().getS3Key());
118-
}
119-
// 3. 레시피 삭제 (Image는 cascade + orphanRemoval로 자동 삭제됨)
120-
recipeRepository.delete(recipe);
121132
}
122133

134+
123135
@Transactional
124136
public void toggleLike(Long memberId, Long recipeId) {
125137
Member member = memberRepository.findById(memberId)
@@ -159,4 +171,46 @@ public List<Recipe> getPublicRecipes() {
159171
return recipeRepository.findByRecipeType(Recipe.RecipeType.PUBLIC);
160172
}
161173

174+
@Transactional(readOnly = true)
175+
public RecipeDto getRecipeFromMemberRecipe(Long memberRecipeId) {
176+
MemberRecipe mr = memberRecipeRepository.findById(memberRecipeId)
177+
.orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_RECIPE_NOT_FOUND));
178+
179+
return RecipeDto.from(mr.getRecipe());
180+
}
181+
182+
@Transactional
183+
public void deleteMemberRecipe(Long memberId, Long memberRecipeId) {
184+
MemberRecipe memberRecipe = memberRecipeRepository.findById(memberRecipeId)
185+
.orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_RECIPE_NOT_FOUND));
186+
187+
// 다른 사람이 요청한 경우 차단
188+
if (!memberRecipe.getMember().getId().equals(memberId)) {
189+
throw BaseException.from(ErrorCode.NOT_RECIPE_OWNER);
190+
}
191+
192+
Recipe recipe = memberRecipe.getRecipe();
193+
194+
// 좋아요만 누른 경우 → 연결만 삭제
195+
if (!memberRecipe.getIsOwner()) {
196+
memberRecipeRepository.delete(memberRecipe);
197+
return;
198+
}
199+
200+
// 소유자인 경우 → 연결 삭제 후 레시피 사용 여부 확인
201+
memberRecipeRepository.delete(memberRecipe);
202+
203+
long count = memberRecipeRepository.countByRecipeId(recipe.getId());
204+
if (count == 0) {
205+
if (recipe.getThumbnailImage() != null) {
206+
s3UploadService.deleteFile(recipe.getThumbnailImage().getS3Key());
207+
}
208+
if (recipe.getSourceImage() != null) {
209+
s3UploadService.deleteFile(recipe.getSourceImage().getS3Key());
210+
}
211+
212+
recipeRepository.delete(recipe);
213+
}
214+
}
215+
162216
}

yechef/src/main/java/sejong/capston/yechef/domain/RecipeSteps/RecipeStep.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
import org.hibernate.annotations.SQLDelete;
77
import org.hibernate.annotations.SQLRestriction;
88
import sejong.capston.yechef.domain.Recipe.Recipe;
9+
import sejong.capston.yechef.global.entity.BaseEntity;
910

1011
@Getter
1112
@Setter
1213
@Entity
1314
@NoArgsConstructor(access = AccessLevel.PROTECTED)
1415
@SQLDelete(sql = "UPDATE recipe_step SET is_deleted = true, deleted_at = now() where id = ?")
1516
@SQLRestriction("is_deleted is FALSE")
16-
public class RecipeStep {
17+
public class RecipeStep extends BaseEntity {
1718

1819
@Id
1920
@GeneratedValue(strategy = GenerationType.IDENTITY)

yechef/src/main/java/sejong/capston/yechef/domain/RecipeSteps/repository/RecipeStepRepository.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import java.util.Optional;
44
import org.springframework.data.jpa.repository.JpaRepository;
5+
import sejong.capston.yechef.domain.MemberRecipe.MemberRecipe;
56
import sejong.capston.yechef.domain.Recipe.Recipe;
67
import sejong.capston.yechef.domain.RecipeSteps.RecipeStep;
78

89
public interface RecipeStepRepository extends JpaRepository<RecipeStep, Long> {
910
Optional<RecipeStep> findByRecipeAndStepNumber(Recipe recipe, int stepNumber);
10-
}
11+
long countByRecipeId(Long recipeId);
12+
}

0 commit comments

Comments
 (0)