Skip to content

Commit 0779e22

Browse files
authored
Merge pull request #84 from Ajou-Hertz/feature/#83-delete-instrument
악기 매물 삭제 API 구현
2 parents 56f2c18 + 65bb905 commit 0779e22

17 files changed

+676
-93
lines changed

src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ public enum CustomExceptionType {
5151
/**
5252
* 악기 관련 예외
5353
*/
54-
INSTRUMENT_NOT_FOUND_BY_ID(2600, "일치하는 매물 정보를 찾을 수 없습니다.")
55-
;
54+
INSTRUMENT_NOT_FOUND_BY_ID(2600, "일치하는 매물 정보를 찾을 수 없습니다."),
55+
INSTRUMENT_DELETE_PERMISSION_DENIED(2601, "악기 매물을 삭제할 권한이 없습니다. 매물은 판매자 본인만 삭제할 수 있습니다.");
5656

5757
private final Integer code;
5858
private final String message;

src/main/java/com/ajou/hertz/common/file/service/AmazonS3FileService.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.util.ArrayList;
6+
import java.util.Collection;
67
import java.util.List;
78
import java.util.UUID;
89

@@ -15,6 +16,7 @@
1516
import com.ajou.hertz.common.properties.AWSProperties;
1617
import com.amazonaws.services.s3.AmazonS3;
1718
import com.amazonaws.services.s3.model.CannedAccessControlList;
19+
import com.amazonaws.services.s3.model.DeleteObjectRequest;
1820
import com.amazonaws.services.s3.model.ObjectMetadata;
1921
import com.amazonaws.services.s3.model.PutObjectRequest;
2022

@@ -56,8 +58,16 @@ public FileDto uploadFile(MultipartFile multipartFile, String uploadPath) {
5658
}
5759

5860
// TODO: 추후 서버 성능 테스트와 함께 @Async 사용한 비동기 로직으로 변경 검토
61+
62+
/**
63+
* 파일(MultipartFile)들을 전달받아 모두 s3 bucket에 업로드한다.
64+
*
65+
* @param multipartFiles 업로드할 파일들
66+
* @param uploadPath 파일들을 업로드할 경로
67+
* @return AWS S3에 저장된 파일들의 정보가 담긴 dto list
68+
*/
5969
@Override
60-
public List<FileDto> uploadFiles(List<MultipartFile> multipartFiles, String uploadPath) {
70+
public List<FileDto> uploadFiles(Iterable<MultipartFile> multipartFiles, String uploadPath) {
6171
List<FileDto> result = new ArrayList<>();
6272
multipartFiles.forEach(multipartFile -> {
6373
FileDto fileUploaded = uploadFile(multipartFile, uploadPath);
@@ -66,11 +76,23 @@ public List<FileDto> uploadFiles(List<MultipartFile> multipartFiles, String uplo
6676
return result;
6777
}
6878

79+
/**
80+
* S3 bucket에서 파일들을 삭제한다.
81+
*
82+
* @param storedFileNames 삭제할 파일들의 이름 목록 (keys of bucket object)
83+
*/
84+
@Override
85+
public void deleteAll(Collection<String> storedFileNames) {
86+
storedFileNames.forEach(storedFileName -> s3Client.deleteObject(
87+
new DeleteObjectRequest(awsProperties.s3().bucketName(), storedFileName)
88+
));
89+
}
90+
6991
/**
7092
* S3 Bucket에 업로드할 고유한 파일 이름을 생성한다.
7193
*
7294
* @param originalFilename 파일의 원래 이름.
73-
* @param uploadPath 업로드 경로
95+
* @param uploadPath 업로드 경로
7496
* @return 생성된 고유한 파일 이름
7597
*/
7698
private String createFileNameToStore(String originalFilename, String uploadPath) {

src/main/java/com/ajou/hertz/common/file/service/FileService.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ajou.hertz.common.file.service;
22

3+
import java.util.Collection;
34
import java.util.List;
45

56
import org.springframework.web.multipart.MultipartFile;
@@ -24,5 +25,12 @@ public interface FileService {
2425
* @param uploadPath 파일들을 업로드할 경로
2526
* @return 저장된 파일들의 정보가 담긴 dto list
2627
*/
27-
List<FileDto> uploadFiles(List<MultipartFile> multipartFiles, String uploadPath);
28+
List<FileDto> uploadFiles(Iterable<MultipartFile> multipartFiles, String uploadPath);
29+
30+
/**
31+
* 파일들을 삭제한다.
32+
*
33+
* @param storedFileNames 삭제할 파일들의 이름 목록
34+
*/
35+
void deleteAll(Collection<String> storedFileNames);
2836
}

src/main/java/com/ajou/hertz/domain/instrument/controller/InstrumentController.java

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.http.MediaType;
1010
import org.springframework.http.ResponseEntity;
1111
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12+
import org.springframework.web.bind.annotation.DeleteMapping;
1213
import org.springframework.web.bind.annotation.GetMapping;
1314
import org.springframework.web.bind.annotation.ModelAttribute;
1415
import org.springframework.web.bind.annotation.PathVariable;
@@ -52,6 +53,9 @@
5253

5354
import io.swagger.v3.oas.annotations.Operation;
5455
import io.swagger.v3.oas.annotations.Parameter;
56+
import io.swagger.v3.oas.annotations.media.Content;
57+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
58+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
5559
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
5660
import io.swagger.v3.oas.annotations.tags.Tag;
5761
import jakarta.validation.Valid;
@@ -411,4 +415,22 @@ public ResponseEntity<AudioEquipmentResponse> createNewAudioEquipmentV1(
411415
.created(URI.create("/instruments/" + audioEquipment.getId()))
412416
.body(InstrumentMapper.toAudioEquipmentResponse(audioEquipment));
413417
}
418+
419+
@Operation(
420+
summary = "악기 매물 삭제",
421+
description = "악기 매물을 삭제합니다. 매물 삭제는 판매자만 할 수 있습니다.",
422+
security = @SecurityRequirement(name = "access-token")
423+
)
424+
@ApiResponses({
425+
@ApiResponse(responseCode = "204"),
426+
@ApiResponse(responseCode = "403", description = "[2601] 악기를 삭제하려는 유저가 판매자가 아닌 경우", content = @Content)
427+
})
428+
@DeleteMapping(value = "/{instrumentId}", headers = API_VERSION_HEADER_NAME + "=" + 1)
429+
public ResponseEntity<Void> deleteInstrumentV1(
430+
@AuthenticationPrincipal UserPrincipal userPrincipal,
431+
@Parameter(description = "Id of instrument", example = "2") @PathVariable Long instrumentId
432+
) {
433+
instrumentCommandService.deleteInstrumentById(userPrincipal.getUserId(), instrumentId);
434+
return ResponseEntity.noContent().build();
435+
}
414436
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.ajou.hertz.domain.instrument.exception;
2+
3+
import com.ajou.hertz.common.exception.ForbiddenException;
4+
import com.ajou.hertz.common.exception.constant.CustomExceptionType;
5+
6+
public class InstrumentDeletePermissionDeniedException extends ForbiddenException {
7+
8+
public InstrumentDeletePermissionDeniedException() {
9+
super(CustomExceptionType.INSTRUMENT_DELETE_PERMISSION_DENIED);
10+
}
11+
}

src/main/java/com/ajou/hertz/domain/instrument/repository/InstrumentHashtagRepository.java

+3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import org.springframework.data.jpa.repository.JpaRepository;
44

5+
import com.ajou.hertz.domain.instrument.entity.Instrument;
56
import com.ajou.hertz.domain.instrument.entity.InstrumentHashtag;
67

78
public interface InstrumentHashtagRepository extends JpaRepository<InstrumentHashtag, Long> {
9+
10+
void deleteAllByInstrument(Instrument instrument);
811
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.ajou.hertz.domain.instrument.repository;
22

3+
import java.util.List;
4+
35
import org.springframework.data.jpa.repository.JpaRepository;
46

57
import com.ajou.hertz.domain.instrument.entity.InstrumentImage;
68

79
public interface InstrumentImageRepository extends JpaRepository<InstrumentImage, Long> {
10+
11+
List<InstrumentImage> findAllByInstrument_Id(Long instrumentId);
812
}

src/main/java/com/ajou/hertz/domain/instrument/service/InstrumentCommandService.java

+21-16
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import org.springframework.transaction.annotation.Transactional;
77
import org.springframework.web.multipart.MultipartFile;
88

9-
import com.ajou.hertz.common.file.service.FileService;
109
import com.ajou.hertz.domain.instrument.dto.AcousticAndClassicGuitarDto;
1110
import com.ajou.hertz.domain.instrument.dto.AmplifierDto;
1211
import com.ajou.hertz.domain.instrument.dto.AudioEquipmentDto;
@@ -29,9 +28,9 @@
2928
import com.ajou.hertz.domain.instrument.entity.Instrument;
3029
import com.ajou.hertz.domain.instrument.entity.InstrumentHashtag;
3130
import com.ajou.hertz.domain.instrument.entity.InstrumentImage;
31+
import com.ajou.hertz.domain.instrument.exception.InstrumentDeletePermissionDeniedException;
3232
import com.ajou.hertz.domain.instrument.mapper.InstrumentMapper;
3333
import com.ajou.hertz.domain.instrument.repository.InstrumentHashtagRepository;
34-
import com.ajou.hertz.domain.instrument.repository.InstrumentImageRepository;
3534
import com.ajou.hertz.domain.instrument.repository.InstrumentRepository;
3635
import com.ajou.hertz.domain.instrument.strategy.AcousticAndClassicGuitarCreationStrategy;
3736
import com.ajou.hertz.domain.instrument.strategy.AmplifierCreationStrategy;
@@ -50,13 +49,11 @@
5049
@Service
5150
public class InstrumentCommandService {
5251

53-
private static final String INSTRUMENT_IMAGE_UPLOAD_PATH = "instrument-image/";
54-
5552
private final UserQueryService userQueryService;
56-
private final FileService fileService;
53+
private final InstrumentQueryService instrumentQueryService;
54+
private final InstrumentImageCommandService instrumentImageCommandService;
5755
private final InstrumentRepository instrumentRepository;
5856
private final InstrumentHashtagRepository instrumentHashtagRepository;
59-
private final InstrumentImageRepository instrumentImageRepository;
6057

6158
/**
6259
* 신규 일렉 기타 매물을 생성 및 저장한다.
@@ -137,6 +134,23 @@ public AudioEquipmentDto createNewAudioEquipment(Long sellerId, CreateNewAudioEq
137134
return InstrumentMapper.toAmplifierDto(audioEquipment);
138135
}
139136

137+
/**
138+
* 악기 매물을 삭제한다.
139+
*
140+
* @param userId 악기 매물을 삭제하려는 유저의 id
141+
* @param instrumentId 삭제할 악기 매물의 id
142+
* @throws InstrumentDeletePermissionDeniedException 악기를 삭제하려는 유저가 판매자가 아닌 경우
143+
*/
144+
public void deleteInstrumentById(Long userId, Long instrumentId) {
145+
Instrument instrument = instrumentQueryService.getInstrumentById(instrumentId);
146+
if (!userId.equals(instrument.getSeller().getId())) {
147+
throw new InstrumentDeletePermissionDeniedException();
148+
}
149+
instrumentImageCommandService.deleteAllByInstrumentId(instrumentId);
150+
instrumentHashtagRepository.deleteAllByInstrument(instrument);
151+
instrumentRepository.delete(instrument);
152+
}
153+
140154
/**
141155
* 신규 악기 매물을 생성하여 저장한다.
142156
*
@@ -164,16 +178,7 @@ private <T extends Instrument, U extends CreateNewInstrumentRequest<T>> T create
164178
* @param images image list
165179
*/
166180
private void registerInstrumentImages(Instrument instrument, List<MultipartFile> images) {
167-
List<InstrumentImage> instrumentImages = fileService
168-
.uploadFiles(images, INSTRUMENT_IMAGE_UPLOAD_PATH)
169-
.stream()
170-
.map(fileDto -> InstrumentImage.create(
171-
instrument,
172-
fileDto.getOriginalName(),
173-
fileDto.getStoredName(),
174-
fileDto.getUrl()
175-
)).toList();
176-
List<InstrumentImage> savedInstrumentImages = instrumentImageRepository.saveAll(instrumentImages);
181+
List<InstrumentImage> savedInstrumentImages = instrumentImageCommandService.saveImages(instrument, images);
177182
instrument.getImages().addAll(savedInstrumentImages);
178183
}
179184

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.ajou.hertz.domain.instrument.service;
2+
3+
import java.util.List;
4+
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.web.multipart.MultipartFile;
7+
8+
import com.ajou.hertz.common.file.service.FileService;
9+
import com.ajou.hertz.domain.instrument.entity.Instrument;
10+
import com.ajou.hertz.domain.instrument.entity.InstrumentImage;
11+
import com.ajou.hertz.domain.instrument.repository.InstrumentImageRepository;
12+
13+
import jakarta.transaction.Transactional;
14+
import lombok.RequiredArgsConstructor;
15+
16+
@RequiredArgsConstructor
17+
@Transactional
18+
@Service
19+
public class InstrumentImageCommandService {
20+
21+
private static final String INSTRUMENT_IMAGE_UPLOAD_PATH = "instrument-image/";
22+
23+
private final InstrumentImageQueryService instrumentImageQueryService;
24+
private final FileService fileService;
25+
private final InstrumentImageRepository instrumentImageRepository;
26+
27+
/**
28+
* 전달받은 multipart files로 instrument image entity를 생성 후 저장한다.
29+
*
30+
* @param instrument 이미지의 대상이 되는 instrument entity
31+
* @param images 저장하고자 하는 image
32+
* @return 저장된 instrument images
33+
*/
34+
public List<InstrumentImage> saveImages(Instrument instrument, Iterable<MultipartFile> images) {
35+
List<InstrumentImage> instrumentImages = fileService
36+
.uploadFiles(images, INSTRUMENT_IMAGE_UPLOAD_PATH)
37+
.stream()
38+
.map(fileDto -> InstrumentImage.create(
39+
instrument,
40+
fileDto.getOriginalName(),
41+
fileDto.getStoredName(),
42+
fileDto.getUrl()
43+
)).toList();
44+
return instrumentImageRepository.saveAll(instrumentImages);
45+
}
46+
47+
/**
48+
* 특정 악기 매물의 모든 이미지를 삭제한다.
49+
*
50+
* @param instrumentId 이미지를 전체 삭제할 악기 매물의 id
51+
*/
52+
public void deleteAllByInstrumentId(Long instrumentId) {
53+
List<InstrumentImage> instrumentImages = instrumentImageQueryService.findAllByInstrumentId(instrumentId);
54+
fileService.deleteAll(
55+
instrumentImages.stream()
56+
.map(InstrumentImage::getStoredName)
57+
.toList()
58+
);
59+
instrumentImageRepository.deleteAll(instrumentImages);
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.ajou.hertz.domain.instrument.service;
2+
3+
import java.util.List;
4+
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.transaction.annotation.Transactional;
7+
8+
import com.ajou.hertz.domain.instrument.entity.InstrumentImage;
9+
import com.ajou.hertz.domain.instrument.repository.InstrumentImageRepository;
10+
11+
import lombok.RequiredArgsConstructor;
12+
13+
@RequiredArgsConstructor
14+
@Transactional(readOnly = true)
15+
@Service
16+
public class InstrumentImageQueryService {
17+
18+
private final InstrumentImageRepository instrumentImageRepository;
19+
20+
/**
21+
* 악기 id를 전달받고, 해당하는 악기의 이미지를 전부 조회한다.
22+
*
23+
* @param instrumentId 이미지를 조회하고자 하는 악기의 id
24+
* @return 조회된 Instrument image entity list
25+
*/
26+
public List<InstrumentImage> findAllByInstrumentId(Long instrumentId) {
27+
return instrumentImageRepository.findAllByInstrument_Id(instrumentId);
28+
}
29+
}

0 commit comments

Comments
 (0)