Skip to content

Commit a42aa53

Browse files
authored
Merge pull request #113 from KB-Hackerton/feature/49
이미지 수정
2 parents 692b94d + 7657d8b commit a42aa53

File tree

8 files changed

+110
-52
lines changed

8 files changed

+110
-52
lines changed

src/main/java/kb_hack/backend/domain/sos/controller/SosController.java

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.List;
44

55
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
6+
import kb_hack.backend.domain.sos.dto.SosUpdateRequest;
67
import kb_hack.backend.global.common.response.success.SuccessResponse;
78
import org.springframework.http.MediaType;
89
import org.springframework.http.ResponseEntity;
@@ -99,43 +100,27 @@ public SuccessResponse<SosCreateResponse> createSos(
99100
@PutMapping(value = "/{sosId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
100101
public SuccessResponse<Void> updateSos(
101102
@PathVariable Long sosId,
102-
// @RequestParam(required = false) String sos_title,
103-
// @RequestParam SosType sos_type,
104-
// @RequestParam String sos_content,
105-
// @RequestParam String expires_at,
106-
// @RequestPart(required = false) List<MultipartFile> images
107-
@Parameter(description = "SOS 요청 제목", example = "급히 타이어 구합니다")
108-
@RequestParam(required = false) String sos_title,
109-
110-
@Parameter(description = "SOS 요청 카테고리", example = "stock",
111-
schema = @Schema(allowableValues = {"stock","labor","equipment","etc"}))
112-
@RequestParam SosType sos_type,
113-
114-
@Parameter(description = "SOS 요청 상세 내용", example = "오늘 안에 타이어 4개 필요합니다")
115-
@RequestParam String sos_content,
116-
117-
@Parameter(description = "요청 만료 시각 (yyyy-MM-dd HH:mm)", example = "2025-09-07 23:59")
118-
@RequestParam (required = false) String expires_at,
119-
120-
@Parameter(description = "첨부 이미지 파일들")
121-
@Schema(type = "string", format = "binary")
122-
@RequestPart(required = false) List<MultipartFile> images
123-
124-
103+
@RequestParam(required = false) String sos_title,
104+
@RequestParam SosType sos_type,
105+
@RequestParam String sos_content,
106+
@RequestParam(required = false) String expires_at,
107+
@RequestParam(required = false) List<Long> deleteImageIds,
108+
@RequestPart(required = false) List<MultipartFile> newImages
125109
) {
126-
Long memberId = getLoginMemberId();
127-
SosCreateRequest req = SosCreateRequest.builder()
128-
.memberId(memberId)
129-
.sosTitle(sos_title)
130-
.sosType(sos_type)
131-
.sosContent(sos_content)
132-
.expiresAt(expires_at)
133-
.images(images)
134-
.build();
110+
SosUpdateRequest req = SosUpdateRequest.builder()
111+
.sosTitle(sos_title)
112+
.sosType(sos_type)
113+
.sosContent(sos_content)
114+
.expiresAt(expires_at)
115+
.deleteImageIds(deleteImageIds)
116+
.newImages(newImages)
117+
.build();
118+
135119
sosService.update(sosId, req);
136120
return SuccessResponse.makeResponse(SuccessStatusCode.SOS_UPDATE_SUCCESS);
137121
}
138122

123+
139124
@Operation(summary = "SOS 삭제", description = "SOS 요청을 완전히 삭제합니다 (하드 딜리트).",security = @SecurityRequirement(name = "bearerAuth"))
140125
@ApiResponses(value = {
141126
@ApiResponse(responseCode = "200", description = "SOS 삭제 성공"),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package kb_hack.backend.domain.sos.dto;
2+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
3+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import jakarta.validation.constraints.NotBlank;
6+
import jakarta.validation.constraints.NotNull;
7+
import kb_hack.backend.domain.sos.entity.SosType;
8+
import lombok.*;
9+
import org.springframework.web.multipart.MultipartFile;
10+
11+
import java.util.List;
12+
13+
@Getter
14+
@Setter
15+
@NoArgsConstructor
16+
@AllArgsConstructor
17+
@Builder
18+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
19+
public class SosUpdateRequest {
20+
21+
@Schema(description = "SOS 요청 제목", example = "급히 타이어 구합니다")
22+
private String sosTitle;
23+
24+
@NotNull
25+
@Schema(description = "SOS 요청 카테고리", example = "stock")
26+
private SosType sosType;
27+
28+
@NotBlank
29+
@Schema(description = "SOS 요청 상세 내용", example = "오늘 안에 타이어 4개 필요합니다")
30+
private String sosContent;
31+
32+
@NotBlank
33+
@Schema(description = "요청 만료 시각 (yyyy-MM-dd HH:mm 또는 HH:mm)", example = "2025-09-07 23:59")
34+
private String expiresAt;
35+
36+
@Schema(description = "삭제할 이미지 ID 리스트", example = "[1,2]")
37+
private List<Long> deleteImageIds;
38+
39+
@Schema(description = "새로 추가할 이미지 파일 리스트")
40+
private List<MultipartFile> newImages;
41+
}

src/main/java/kb_hack/backend/domain/sos/mapper/SosImageMapper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.util.List;
44

55
import kb_hack.backend.domain.sos.entity.SosImage;
6+
7+
import org.apache.ibatis.annotations.Delete;
68
import org.apache.ibatis.annotations.Insert;
79
import org.apache.ibatis.annotations.Mapper;
810
import org.apache.ibatis.annotations.Options;
@@ -17,8 +19,13 @@ public interface SosImageMapper {
1719
@Select("SELECT storage_key FROM sos_image WHERE sos_id = #{sosId}")
1820
List<String> findImageKeysBySosId(Long sosId);
1921

22+
@Select("SELECT * FROM sos_image WHERE sos_image_id = #{sosImageId}")
23+
SosImage findById(Long sosImageId);
2024

2125
List<SosImage> findBySosId(Long sosId);
2226

2327
void deleteBySosId(Long sosId);
28+
29+
@Delete("DELETE FROM sos_image WHERE sos_image_id = #{sosImageId}")
30+
void deleteById(Long sosImageId);
2431
}

src/main/java/kb_hack/backend/domain/sos/service/SosService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
import kb_hack.backend.domain.sos.dto.SosCreateResponse;
77
import kb_hack.backend.domain.sos.dto.SosDetailResponse;
88
import kb_hack.backend.domain.sos.dto.SosListResponse;
9+
import kb_hack.backend.domain.sos.dto.SosUpdateRequest;
910

1011
public interface SosService {
1112
SosCreateResponse create(SosCreateRequest req);
12-
void update(Long sosId, SosCreateRequest req);
13+
void update(Long sosId, SosUpdateRequest req);
1314

1415
void hardDelete(Long sosId);
1516

src/main/java/kb_hack/backend/domain/sos/service/SosServiceImpl.java

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import kb_hack.backend.domain.sos.dto.SosDetailResponse;
77
import kb_hack.backend.domain.sos.dto.SosDetailRow;
88
import kb_hack.backend.domain.sos.dto.SosListResponse;
9+
import kb_hack.backend.domain.sos.dto.SosUpdateRequest;
910
import kb_hack.backend.domain.sos.entity.Sos;
1011
import kb_hack.backend.domain.sos.entity.SosImage;
1112
import kb_hack.backend.domain.sos.entity.SosType;
@@ -93,45 +94,56 @@ public SosCreateResponse create(SosCreateRequest req) {
9394

9495
@Override
9596
@Transactional
96-
public void update(Long sosId, SosCreateRequest req) {
97+
public void update(Long sosId, SosUpdateRequest req) {
9798
// 1) 만료일 파싱
9899
LocalDateTime expiresAt = parseExpiresAt(req.getExpiresAt());
99100

100101
// 2) SOS row 수정
101102
Sos sos = Sos.builder()
102-
.sosId(sosId)
103-
.sosTitle(req.getSosTitle())
104-
.sosType(req.getSosType())
105-
.sosContent(req.getSosContent())
106-
.expiresAt(expiresAt)
107-
.build();
103+
.sosId(sosId)
104+
.sosTitle(req.getSosTitle())
105+
.sosType(req.getSosType())
106+
.sosContent(req.getSosContent())
107+
.expiresAt(expiresAt)
108+
.build();
108109

109110
int updated = sosMapper.update(sos);
110111
if (updated == 0) {
111112
throw new IllegalArgumentException("해당 SOS가 존재하지 않거나 삭제된 상태입니다.");
112113
}
113114

115+
// 3) 삭제할 이미지 처리 (DB + S3)
116+
if (req.getDeleteImageIds() != null) {
117+
for (Long imageId : req.getDeleteImageIds()) {
118+
SosImage image = sosImageMapper.findById(imageId);
119+
if (image != null) {
120+
storageService.delete(image.getStorageKey()); // S3 삭제
121+
sosImageMapper.deleteById(imageId); // DB 삭제
122+
}
123+
}
124+
}
114125

115-
List<MultipartFile> files = req.getImages();
116-
if (files != null && !files.isEmpty()) {
117-
// 기존 이미지 삭제
118-
List<SosImage> oldImages = sosImageMapper.findBySosId(sosId);
119-
120-
sosImageMapper.deleteBySosId(sosId);
121-
122-
List<String> keys = storageService.uploadAll(files, sosId);
126+
// 4) 새 이미지 업로드 → insert
127+
if (req.getNewImages() != null && !req.getNewImages().isEmpty()) {
128+
List<String> keys = storageService.uploadAll(req.getNewImages(), sosId);
123129
for (String key : keys) {
124130
SosImage image = SosImage.builder()
125-
.sosId(sosId)
126-
.storageKey(key)
127-
.isDeleted(false)
128-
.build();
131+
.sosId(sosId)
132+
.storageKey(key)
133+
.build();
129134
sosImageMapper.insert(image);
130135
}
131136
}
137+
138+
// 5) 최종 개수 검증
139+
List<SosImage> currentImages = sosImageMapper.findBySosId(sosId);
140+
if (currentImages.size() > 3) {
141+
throw new IllegalArgumentException("이미지는 최대 3개까지 저장할 수 있습니다.");
142+
}
132143
}
133144

134145

146+
135147
@Override
136148
@Transactional
137149
public void hardDelete(Long sosId) {

src/main/java/kb_hack/backend/domain/sos/service/StorageService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
public interface StorageService {
77
// 업로드 후 "storage_key" 들을 반환 (예: "sos/2025/09/01/uuid.jpg")
88
List<String> uploadAll(List<MultipartFile> files,Long sosId);
9+
void delete(String key);
910
}

src/main/java/kb_hack/backend/domain/sos/service/StorageServiceStub.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,15 @@ public List<String> uploadAll(List<MultipartFile> files, Long sosId) {
5454
}
5555
}).collect(Collectors.toList());
5656
}
57+
58+
@Override
59+
public void delete(String key) {
60+
try {
61+
s3Template.deleteObject(bucket, key.replace("https://" + bucket + ".s3." + region + ".amazonaws.com/", ""));
62+
} catch (Exception e) {
63+
throw new ServerErrorException(BadStatusCode.FILE_DELETE_FAILED_EXCEPTION);
64+
}
65+
}
66+
5767
}
5868

src/main/java/kb_hack/backend/global/common/exception/enums/BadStatusCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public enum BadStatusCode {
6464
FAIL_TO_SEND_ALARM(HttpStatus.INTERNAL_SERVER_ERROR,"사용자가 수신거부한 알림입니다."),
6565
FAIL_TO_SEND_ALARM2(HttpStatus.INTERNAL_SERVER_ERROR,"사용자가 수신거부한 시간대 입니다"),
6666
FAILT_TO_GET_RANKING_ANNOUNCE(HttpStatus.INTERNAL_SERVER_ERROR,"인기 공고 불러오는데 실패 했습니다."),
67+
FILE_DELETE_FAILED_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR,"파일 삭제에 실패했습니다."),
6768

6869

6970
// 크롤링 관련 5xx

0 commit comments

Comments
 (0)