Skip to content

Commit 21e038d

Browse files
committed
Refactor: 멘토링 생성 시 이미지 s3 업로드 및 url db 저장
1 parent 740dba2 commit 21e038d

File tree

7 files changed

+97
-22
lines changed

7 files changed

+97
-22
lines changed

back/src/main/java/com/back/domain/mentoring/mentoring/controller/MentoringController.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import jakarta.validation.Valid;
1515
import lombok.RequiredArgsConstructor;
1616
import org.springframework.data.domain.Page;
17+
import org.springframework.http.MediaType;
1718
import org.springframework.security.access.prepost.PreAuthorize;
1819
import org.springframework.web.bind.annotation.*;
20+
import org.springframework.web.multipart.MultipartFile;
1921

2022
import java.util.List;
2123

@@ -73,14 +75,15 @@ public RsData<MentoringResponse> getMentoring(
7375
);
7476
}
7577

76-
@PostMapping
78+
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
7779
@PreAuthorize("hasRole('MENTOR')")
7880
@Operation(summary = "멘토링 생성", description = "멘토링을 생성합니다. 로그인한 멘토만 생성할 수 있습니다.")
7981
public RsData<MentoringResponse> createMentoring(
80-
@RequestBody @Valid MentoringRequest reqDto
82+
@RequestPart("reqDto") @Valid MentoringRequest reqDto,
83+
@RequestPart(value = "thumb", required = false) MultipartFile thumb
8184
) {
8285
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());
83-
MentoringResponse resDto = mentoringService.createMentoring(reqDto, mentor);
86+
MentoringResponse resDto = mentoringService.createMentoring(reqDto, thumb, mentor);
8487

8588
return new RsData<>(
8689
"201",

back/src/main/java/com/back/domain/mentoring/mentoring/entity/Mentoring.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@ public class Mentoring extends BaseEntity {
3434
private double rating = 0.0;
3535

3636
@Builder
37-
public Mentoring(Mentor mentor, String title, String bio, String thumb) {
37+
public Mentoring(Mentor mentor, String title, String bio) {
3838
this.mentor = mentor;
3939
this.title = title;
4040
this.bio = bio;
41-
this.thumb = thumb;
4241
}
4342

4443
public void update(String title, String bio, List<Tag> tags, String thumb) {
@@ -59,6 +58,10 @@ public void updateTags(List<Tag> tags) {
5958
}
6059
}
6160

61+
public void updateThumb(String thumb) {
62+
this.thumb = thumb;
63+
}
64+
6265
public void updateRating(double averageRating) {
6366
this.rating = averageRating;
6467
}

back/src/main/java/com/back/domain/mentoring/mentoring/service/MentoringService.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.back.domain.mentoring.mentoring.dto.response.MentoringResponse;
99
import com.back.domain.mentoring.mentoring.entity.Mentoring;
1010
import com.back.domain.mentoring.mentoring.entity.Tag;
11+
import com.back.domain.mentoring.mentoring.error.ImageErrorCode;
1112
import com.back.domain.mentoring.mentoring.error.MentoringErrorCode;
1213
import com.back.domain.mentoring.mentoring.repository.MentoringRepository;
1314
import com.back.domain.mentoring.mentoring.repository.TagRepository;
@@ -18,7 +19,9 @@
1819
import org.springframework.data.domain.Pageable;
1920
import org.springframework.stereotype.Service;
2021
import org.springframework.transaction.annotation.Transactional;
22+
import org.springframework.web.multipart.MultipartFile;
2123

24+
import java.io.IOException;
2225
import java.util.ArrayList;
2326
import java.util.List;
2427
import java.util.Set;
@@ -30,6 +33,7 @@ public class MentoringService {
3033
private final MentoringRepository mentoringRepository;
3134
private final MentoringStorage mentoringStorage;
3235
private final TagRepository tagRepository;
36+
private final S3ImageUploader s3ImageUploader;
3337

3438
@Transactional(readOnly = true)
3539
public Page<MentoringWithTagsDto> getMentorings(String keyword, int page, int size) {
@@ -58,20 +62,31 @@ public MentoringResponse getMentoring(Long mentoringId) {
5862
}
5963

6064
@Transactional
61-
public MentoringResponse createMentoring(MentoringRequest reqDto, Mentor mentor) {
65+
public MentoringResponse createMentoring(MentoringRequest reqDto, MultipartFile thumb, Mentor mentor) {
6266
validateMentoringTitle(mentor.getId(), reqDto.title());
6367

6468
Mentoring mentoring = Mentoring.builder()
6569
.mentor(mentor)
6670
.title(reqDto.title())
6771
.bio(reqDto.bio())
68-
.thumb(reqDto.thumb())
6972
.build();
7073

7174
List<Tag> tags = getOrCreateTags(reqDto.tags());
7275
mentoring.updateTags(tags);
7376

74-
mentoringRepository.save(mentoring);
77+
mentoringRepository.saveAndFlush(mentoring);
78+
79+
if (thumb != null && !thumb.isEmpty()) {
80+
String imageUrl = null;
81+
try {
82+
String path = "mentoring/" + mentoring.getId();
83+
imageUrl = s3ImageUploader.upload(thumb, path);
84+
} catch (IOException e) {
85+
throw new ServiceException(ImageErrorCode.IMAGE_UPLOAD_FAILED);
86+
}
87+
88+
mentoring.updateThumb(imageUrl);
89+
}
7590

7691
return new MentoringResponse(
7792
MentoringDetailDto.from(mentoring),

back/src/main/java/com/back/global/initData/SessionInitData.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import com.back.domain.mentoring.slot.service.MentorSlotService;
1717
import lombok.RequiredArgsConstructor;
1818
import org.springframework.boot.CommandLineRunner;
19-
import org.springframework.context.annotation.Bean;
2019
import org.springframework.context.annotation.Configuration;
2120

2221
import java.time.LocalDateTime;
@@ -45,7 +44,7 @@ public CommandLineRunner initData() {
4544

4645
// 멘토링 생성
4746
MentoringRequest mentoringRequest = new MentoringRequest("Test Mentoring", Arrays.asList("Java", "Spring"), "This is a test mentoring.", null);
48-
MentoringResponse mentoringResponse = mentoringService.createMentoring(mentoringRequest, mentor);
47+
MentoringResponse mentoringResponse = mentoringService.createMentoring(mentoringRequest, null, mentor);
4948

5049
// 멘토 슬롯 생성
5150
MentorSlotRequest mentorSlotRequest = new MentorSlotRequest(mentor.getId(), LocalDateTime.now().plusDays(1), LocalDateTime.now().plusDays(1).plusHours(1));

back/src/test/java/com/back/domain/mentoring/mentoring/controller/MentoringControllerTest.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
2121
import org.springframework.boot.test.context.SpringBootTest;
2222
import org.springframework.http.MediaType;
23+
import org.springframework.mock.web.MockMultipartFile;
2324
import org.springframework.test.context.ActiveProfiles;
2425
import org.springframework.test.web.servlet.MockMvc;
2526
import org.springframework.test.web.servlet.ResultActions;
2627
import org.springframework.transaction.annotation.Transactional;
2728

29+
import java.nio.charset.StandardCharsets;
2830
import java.util.List;
2931

3032
import static org.assertj.core.api.Assertions.assertThat;
@@ -340,17 +342,22 @@ private ResultActions performCreateMentoring(String token) throws Exception {
340342
{
341343
"title": "Spring Boot 멘토링",
342344
"tags": ["Spring", "Java"],
343-
"bio": "Spring Boot를 활용한 백엔드 개발 입문",
344-
"thumb": "https://example.com/thumb.jpg"
345+
"bio": "Spring Boot를 활용한 백엔드 개발 입문"
345346
}
346347
""";
347348

349+
MockMultipartFile jsonPart = new MockMultipartFile(
350+
"reqDto",
351+
null,
352+
MediaType.APPLICATION_JSON_VALUE,
353+
req.getBytes(StandardCharsets.UTF_8)
354+
);
355+
348356
return mvc
349357
.perform(
350-
post(MENTORING_URL)
358+
multipart(MENTORING_URL)
359+
.file(jsonPart)
351360
.cookie(new Cookie(TOKEN, token))
352-
.contentType(MediaType.APPLICATION_JSON)
353-
.content(req)
354361
)
355362
.andDo(print())
356363
.andExpect(handler().handlerType(MentoringController.class))

back/src/test/java/com/back/domain/mentoring/mentoring/service/MentoringServiceTest.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
import org.springframework.data.domain.PageImpl;
2828
import org.springframework.data.domain.PageRequest;
2929
import org.springframework.data.domain.Pageable;
30+
import org.springframework.mock.web.MockMultipartFile;
31+
import org.springframework.test.util.ReflectionTestUtils;
3032

33+
import java.io.IOException;
3134
import java.util.List;
3235

3336
import static org.assertj.core.api.Assertions.assertThat;
@@ -50,6 +53,9 @@ class MentoringServiceTest {
5053
@Mock
5154
private MentoringStorage mentoringStorage;
5255

56+
@Mock
57+
private S3ImageUploader s3ImageUploader;
58+
5359
private Mentor mentor1, mentor2;
5460
private Mentoring mentoring1;
5561
private MentoringRequest request;
@@ -199,17 +205,58 @@ void createMentoring() {
199205
.thenReturn(false);
200206

201207
// when
202-
MentoringResponse result = mentoringService.createMentoring(request, mentor1);
208+
MentoringResponse result = mentoringService.createMentoring(request, null, mentor1);
203209

204210
// then
205211
assertThat(result).isNotNull();
206212
assertThat(result.mentoring().title()).isEqualTo(request.title());
207213
assertThat(result.mentoring().bio()).isEqualTo(request.bio());
208214
assertThat(result.mentoring().tags()).isEqualTo(request.tags());
209-
assertThat(result.mentoring().thumb()).isEqualTo(request.thumb());
210215
verify(mentoringRepository).existsByMentorIdAndTitle(mentor1.getId(), request.title());
211216
verify(tagRepository).findByNameIn(request.tags());
212-
verify(mentoringRepository).save(any(Mentoring.class));
217+
verify(mentoringRepository).saveAndFlush(any(Mentoring.class));
218+
}
219+
220+
@Test
221+
@DisplayName("생성 성공 - 썸네일 포함")
222+
void createMentoringWithImage() throws IOException {
223+
// given
224+
List<Tag> tags = TagFixture.createDefaultTags();
225+
MockMultipartFile image = new MockMultipartFile(
226+
"thumb",
227+
"test.jpg",
228+
"image/jpeg",
229+
"test-image-content".getBytes()
230+
);
231+
String expectedImageUrl = "https://fivelogic-files-bucket.s3.amazonaws.com/images/mentoring/%d".formatted(mentoring1.getId());
232+
233+
when(tagRepository.findByNameIn(request.tags()))
234+
.thenReturn(tags);
235+
when(mentoringRepository.existsByMentorIdAndTitle(mentor1.getId(), request.title()))
236+
.thenReturn(false);
237+
when(mentoringRepository.saveAndFlush(any(Mentoring.class)))
238+
.thenAnswer(invocation -> {
239+
Mentoring mentoring = invocation.getArgument(0);
240+
ReflectionTestUtils.setField(mentoring, "id", mentoring1.getId());
241+
return mentoring;
242+
});
243+
when(s3ImageUploader.upload(eq(image), anyString()))
244+
.thenReturn(expectedImageUrl);
245+
246+
// when
247+
MentoringResponse result = mentoringService.createMentoring(request, image, mentor1);
248+
249+
// then
250+
assertThat(result).isNotNull();
251+
assertThat(result.mentoring().title()).isEqualTo(request.title());
252+
assertThat(result.mentoring().bio()).isEqualTo(request.bio());
253+
assertThat(result.mentoring().tags()).isEqualTo(request.tags());
254+
assertThat(result.mentoring().thumb()).isEqualTo(expectedImageUrl);
255+
256+
verify(mentoringRepository).existsByMentorIdAndTitle(mentor1.getId(), request.title());
257+
verify(tagRepository).findByNameIn(request.tags());
258+
verify(mentoringRepository).saveAndFlush(any(Mentoring.class));
259+
verify(s3ImageUploader).upload(eq(image), eq("mentoring/1"));
213260
}
214261

215262
@Test
@@ -220,7 +267,7 @@ void throwExceptionWhenAlreadyExists() {
220267
.thenReturn(true);
221268

222269
// when & then
223-
assertThatThrownBy(() -> mentoringService.createMentoring(request, mentor1))
270+
assertThatThrownBy(() -> mentoringService.createMentoring(request, null, mentor1))
224271
.isInstanceOf(ServiceException.class)
225272
.hasFieldOrPropertyWithValue("resultCode", MentoringErrorCode.ALREADY_EXISTS_MENTORING.getCode());
226273
verify(mentoringRepository).existsByMentorIdAndTitle(mentor1.getId(), request.title());

back/src/test/java/com/back/fixture/mentoring/MentoringFixture.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ public static Mentoring create(Mentor mentor) {
1919
.mentor(mentor)
2020
.title(DEFAULT_TITLE)
2121
.bio(DEFAULT_BIO)
22-
.thumb(DEFAULT_THUMB)
2322
.build();
2423
mentoring.updateTags(DEFAULT_TAGS);
24+
mentoring.updateThumb(DEFAULT_THUMB);
25+
2526
return mentoring;
2627
}
2728

@@ -30,9 +31,9 @@ public static Mentoring create(Long id, Mentor mentor) {
3031
.mentor(mentor)
3132
.title(DEFAULT_TITLE)
3233
.bio(DEFAULT_BIO)
33-
.thumb(DEFAULT_THUMB)
3434
.build();
3535
mentoring.updateTags(DEFAULT_TAGS);
36+
mentoring.updateThumb(DEFAULT_THUMB);
3637

3738
ReflectionTestUtils.setField(mentoring, "id", id);
3839
return mentoring;
@@ -43,9 +44,9 @@ public static Mentoring create(Long id, Mentor mentor, String title, String bio,
4344
.mentor(mentor)
4445
.title(DEFAULT_TITLE)
4546
.bio(DEFAULT_BIO)
46-
.thumb(DEFAULT_THUMB)
4747
.build();
4848
mentoring.updateTags(DEFAULT_TAGS);
49+
mentoring.updateThumb(DEFAULT_THUMB);
4950

5051
if (id != null) {
5152
ReflectionTestUtils.setField(mentoring, "id", id);

0 commit comments

Comments
 (0)