Skip to content

Commit 6abefcf

Browse files
committed
Merge branch 'develop' into feat/#15
2 parents cec5722 + 43f6846 commit 6abefcf

File tree

10 files changed

+210
-18
lines changed

10 files changed

+210
-18
lines changed

src/main/java/hackathon/soa/config/AmazonConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public class AmazonConfig {
3939
@Value("tests")
4040
private String testsPath; // S3 내 테스트 관련 파일 저장 디렉토리 경로 (폴더명: tests)
4141

42+
@Value("stories")
43+
private String storiesPath; // S3 내 테스트 관련 파일 저장 디렉토리 경로 (폴더명: stories)
44+
4245
//!! s3에 어떤 디렉토리를 만들고, 그안에 뭘 저장하고 싶다면!!
4346
//1. aws 콘솔에서 s3 디렉토리 생성
4447
//2. AmazonConfig에 private String ~~Path 변수 생성
@@ -69,6 +72,7 @@ public AmazonS3 amazonS3() {
6972
.build();
7073
}
7174

75+
7276
/**
7377
* 인증 정보를 스프링 컨테이너에 빈으로 등록하는 메서드.
7478
* 다른 AWS 서비스에서 이 인증 정보를 참조하여 사용할 수 있도록 함.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package hackathon.soa.domain.story;
2+
3+
import hackathon.soa.common.apiPayload.ApiResponse;
4+
import hackathon.soa.domain.story.dto.StoryRequestDTO;
5+
import hackathon.soa.domain.story.dto.StoryResponseDTO;
6+
import hackathon.soa.domain.test.TestService;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.Parameter;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.data.domain.Page;
12+
import org.springframework.data.domain.PageRequest;
13+
import org.springframework.data.domain.Pageable;
14+
import org.springframework.http.MediaType;
15+
import org.springframework.web.bind.annotation.*;
16+
import org.springframework.web.multipart.MultipartFile;
17+
18+
@Tag(name = "Story", description = "스토리 관련 API")
19+
@RestController
20+
@RequestMapping("/api/stories")
21+
@RequiredArgsConstructor
22+
public class StoryController {
23+
24+
private final TestService testService;
25+
private final StoryService storyService;
26+
27+
private final Long userId = 1L;
28+
29+
@Operation(summary = "스토리 이미지 업로드", description = "MultipartFile로 전달된 이미지를 S3에 업로드하고 URL을 반환합니다.")
30+
@PostMapping(value = "/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
31+
public ApiResponse<String> uploadImage(
32+
@Parameter(description = "업로드할 이미지 파일", required = true)
33+
@RequestPart("image") MultipartFile image) {
34+
35+
String url = testService.uploadTestImage(image, userId);
36+
return ApiResponse.onSuccess(url);
37+
}
38+
@Operation(summary = "스토리 목록 조회", description = "스토리 목록을 10개씩 페이지네이션으로 반환합니다.")
39+
@GetMapping
40+
public ApiResponse<Page<StoryResponseDTO.StoryListDTO>> getStoryList(
41+
@RequestParam(defaultValue = "0") int page,
42+
@RequestParam(defaultValue = "10") int size) {
43+
44+
Pageable pageable = PageRequest.of(page, size);
45+
Page<StoryResponseDTO.StoryListDTO> stories = storyService.getStories(pageable);
46+
return ApiResponse.onSuccess(stories);
47+
}
48+
49+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package hackathon.soa.domain.story;
2+
3+
import hackathon.soa.entity.Story;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface StoryRepository extends JpaRepository<Story, Long> {
7+
// 필요한 경우, 회원 ID로 해당 사용자의 스토리를 찾는 메서드도 정의 가능
8+
// List<Story> findByMemberId(Long memberId);
9+
}
10+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package hackathon.soa.domain.story;
2+
3+
import hackathon.soa.config.AmazonConfig;
4+
import hackathon.soa.domain.s3.AmazonS3Manager;
5+
import hackathon.soa.domain.story.dto.StoryResponseDTO;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.web.multipart.MultipartFile;
9+
import org.springframework.data.domain.Pageable;
10+
import org.springframework.data.domain.Page;
11+
import org.springframework.data.domain.PageRequest;
12+
13+
14+
import java.util.UUID;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
public class StoryService {
19+
20+
private final AmazonS3Manager amazonS3Manager;
21+
private final AmazonConfig amazonConfig;
22+
23+
public String uploadTestImage(MultipartFile image) {
24+
// 파일명을 고유하게 생성: 예) tests/uuid.jpg
25+
String keyName = generateTestImageKeyName(image.getOriginalFilename());
26+
27+
// S3에 업로드 및 URL 반환
28+
return amazonS3Manager.uploadFile(keyName, image);
29+
}
30+
31+
private String generateTestImageKeyName(String originalFileName) {
32+
String ext = originalFileName.substring(originalFileName.lastIndexOf(".")); // .jpg, .png 등
33+
return amazonConfig.getTestsPath() + "/" + UUID.randomUUID() + ext; // tests/uuid.jpg
34+
}
35+
36+
private final StoryRepository storyRepository;
37+
38+
public Page<StoryResponseDTO.StoryListDTO> getStories(Pageable pageable) {
39+
return storyRepository.findAll(pageable)
40+
.map(story -> StoryResponseDTO.StoryListDTO.builder()
41+
.memberId(story.getMember().getId())
42+
.nickname(story.getMember().getNickname())
43+
.profileImageUrl(story.getMember().getProfileImageUrl())
44+
.imageUrl(story.getImageUrl())
45+
.build());
46+
}
47+
48+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package hackathon.soa.domain.story.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import org.springframework.web.multipart.MultipartFile;
8+
9+
public class StoryRequestDTO {
10+
11+
@Getter
12+
@Builder
13+
@NoArgsConstructor
14+
@AllArgsConstructor
15+
public static class UploadRequest {
16+
private Long memberId; // 회원 ID
17+
private MultipartFile image; // 업로드할 이미지
18+
}
19+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package hackathon.soa.domain.story.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
public class StoryResponseDTO {
9+
10+
@Getter
11+
@AllArgsConstructor
12+
public static class ImageUploadResultDTO {
13+
private String imageUrl;
14+
}
15+
16+
@Getter
17+
@Builder
18+
@AllArgsConstructor
19+
public static class StoryListDTO {
20+
private Long memberId;
21+
private String nickname;
22+
private String profileImageUrl;
23+
private String imageUrl;
24+
}
25+
}

src/main/java/hackathon/soa/domain/test/TestController.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package hackathon.soa.domain.test;
22

3-
import hackathon.soa.common.apiPayload.ApiResponse;
43
import hackathon.soa.common.JwtUser;
4+
import hackathon.soa.common.apiPayload.ApiResponse;
55
import hackathon.soa.domain.test.dto.TestResponseDTO;
66
import io.swagger.v3.oas.annotations.Operation;
7-
import io.swagger.v3.oas.annotations.Parameter;
87
import io.swagger.v3.oas.annotations.tags.Tag;
98
import lombok.RequiredArgsConstructor;
10-
import org.springframework.http.MediaType;
11-
import org.springframework.web.bind.annotation.*;
12-
import org.springframework.web.multipart.MultipartFile;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RestController;
1312

1413

1514
@Tag(name = "Test", description = "테스트용 API")
@@ -25,15 +24,6 @@ public ApiResponse<TestResponseDTO.TestDTO> test() {
2524
return ApiResponse.onSuccess(TestConverter.toTempTestDTO());
2625
}
2726

28-
@Operation(summary = "테스트용 이미지 업로드", description = "MultipartFile로 전달된 이미지를 S3의 tests 디렉토리에 업로드하고 URL을 반환합니다.")
29-
@PostMapping(value = "/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
30-
public ApiResponse<String> uploadImage(
31-
@Parameter(description = "업로드할 이미지 파일", required = true)
32-
@RequestPart("image") MultipartFile image) {
33-
34-
String url = testService.uploadTestImage(image);
35-
return ApiResponse.onSuccess(url);
36-
}
3727

3828
@GetMapping("/Jwt")
3929
@Operation(summary = "JWT 토큰 테스트", description = "JWT 토큰을 통해 현재 사용자의 memberId를 반환합니다.")

src/main/java/hackathon/soa/domain/test/TestService.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import hackathon.soa.common.apiPayload.exception.TempHandler;
44
import hackathon.soa.config.AmazonConfig;
5+
import hackathon.soa.domain.member.MemberRepository;
56
import hackathon.soa.domain.s3.AmazonS3Manager;
7+
import hackathon.soa.domain.story.StoryRepository;
8+
import hackathon.soa.entity.Member;
9+
import hackathon.soa.entity.Story;
610
import lombok.RequiredArgsConstructor;
711
import org.springframework.stereotype.Service;
812
import org.springframework.web.multipart.MultipartFile;
@@ -15,13 +19,28 @@ public class TestService {
1519

1620
private final AmazonS3Manager amazonS3Manager;
1721
private final AmazonConfig amazonConfig;
22+
private final MemberRepository memberRepository;
23+
private final StoryRepository storyRepository;
1824

19-
public String uploadTestImage(MultipartFile image) {
20-
// 파일명을 고유하게 생성: 예) tests/uuid.jpg
25+
public String uploadTestImage(MultipartFile image, Long userId) {
26+
// 1. 파일명 생성
2127
String keyName = generateTestImageKeyName(image.getOriginalFilename());
2228

23-
// S3에 업로드 및 URL 반환
24-
return amazonS3Manager.uploadFile(keyName, image);
29+
// 2. S3에 업로드
30+
String imageUrl = amazonS3Manager.uploadFile(keyName, image);
31+
32+
// 3. Member 조회
33+
Member member = memberRepository.findById(userId)
34+
.orElseThrow(() -> new IllegalArgumentException("해당 ID의 회원이 존재하지 않습니다: " + userId));
35+
36+
// 4. Story 생성 및 저장
37+
Story story = Story.builder()
38+
.imageUrl(imageUrl)
39+
.member(member)
40+
.build();
41+
storyRepository.save(story);
42+
43+
return imageUrl;
2544
}
2645

2746
private String generateTestImageKeyName(String originalFileName) {

src/main/java/hackathon/soa/entity/Member.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,7 @@ public class Member extends BaseEntity {
7373
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
7474
private List<Likes> likes = new ArrayList<>();
7575

76+
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
77+
private List<Story> stories = new ArrayList<>();
78+
7679
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package hackathon.soa.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
6+
@Entity
7+
@Table(name = "story")
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
@Getter
11+
@Builder
12+
public class Story extends BaseEntity {
13+
14+
@Id
15+
@GeneratedValue(strategy = GenerationType.IDENTITY)
16+
private Long id;
17+
18+
@Column(nullable = false, name = "image_url", columnDefinition = "varchar(255)")
19+
private String imageUrl;
20+
21+
@ManyToOne(fetch = FetchType.LAZY)
22+
@JoinColumn(name = "member_id", nullable = false)
23+
private Member member;
24+
}
25+

0 commit comments

Comments
 (0)