-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DDING-53] Feed 생성 API 구현 #197
Changes from all commits
fa87eee
4083061
a1f30d8
26e903e
150d763
36f59eb
2fa135e
dd4dc19
23393da
bfbb261
63d48e7
f0e6450
af1b866
c8840b4
3992d0b
e1f58e1
b539e5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package ddingdong.ddingdongBE.domain.feed.api; | ||
|
||
import ddingdong.ddingdongBE.auth.PrincipalDetails; | ||
import ddingdong.ddingdongBE.domain.feed.controller.dto.request.CreateFeedRequest; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.security.SecurityRequirement; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.validation.Valid; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
||
@Tag(name = "Feed - Club", description = "Feed API") | ||
@RequestMapping("/server") | ||
public interface ClubFeedApi { | ||
|
||
@Operation(summary = "동아리 피드 생성 API") | ||
@ApiResponse(responseCode = "201", description = "동아리 피드 생성 성공") | ||
@ResponseStatus(HttpStatus.CREATED) | ||
@SecurityRequirement(name = "AccessToken") | ||
@PostMapping("/central/clubs/feeds") | ||
void createFeed( | ||
@RequestBody @Valid CreateFeedRequest createFeedRequest, | ||
@AuthenticationPrincipal PrincipalDetails principalDetails | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package ddingdong.ddingdongBE.domain.feed.controller; | ||
|
||
import ddingdong.ddingdongBE.auth.PrincipalDetails; | ||
import ddingdong.ddingdongBE.domain.feed.api.ClubFeedApi; | ||
import ddingdong.ddingdongBE.domain.feed.controller.dto.request.CreateFeedRequest; | ||
import ddingdong.ddingdongBE.domain.feed.service.FacadeClubFeedService; | ||
import ddingdong.ddingdongBE.domain.user.entity.User; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class ClubFeedController implements ClubFeedApi { | ||
|
||
private final FacadeClubFeedService facadeClubFeedService; | ||
|
||
@Override | ||
public void createFeed(CreateFeedRequest createFeedRequest, PrincipalDetails principalDetails) { | ||
User user = principalDetails.getUser(); | ||
facadeClubFeedService.create(createFeedRequest.toCommand(user)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package ddingdong.ddingdongBE.domain.feed.controller.dto.request; | ||
|
||
import ddingdong.ddingdongBE.domain.feed.service.dto.command.CreateFeedCommand; | ||
import ddingdong.ddingdongBE.domain.user.entity.User; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotNull; | ||
|
||
public record CreateFeedRequest( | ||
@Schema(description = "활동 내용", example = "저희의 활동 내용은 이것입니다.") | ||
@NotNull(message = "activityContent는 null이 될 수 없습니다.") | ||
String activityContent, | ||
@Schema(description = "이미지/비디오 식별자 id", example = "0192c828-ffce-7ee8-94a8-d9d4c8cdec00") | ||
@NotNull(message = "mediaId는 null이 될 수 없습니다.") | ||
String mediaId, | ||
@Schema(description = "컨텐츠 종류", example = "IMAGE") | ||
@NotNull(message = "contentType은 null이 될 수 없습니다.") | ||
String contentType | ||
) { | ||
|
||
public CreateFeedCommand toCommand(User user) { | ||
return CreateFeedCommand.builder() | ||
.activityContent(activityContent) | ||
.mediaId(mediaId) | ||
.contentType(contentType) | ||
.user(user) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,14 @@ | ||
package ddingdong.ddingdongBE.domain.feed.entity; | ||
|
||
import java.util.Arrays; | ||
|
||
public enum FeedType { | ||
IMAGE, VIDEO | ||
IMAGE, VIDEO; | ||
|
||
public static FeedType findByContentType(String contentType) { | ||
return Arrays.stream(values()) | ||
.filter(feedType -> feedType.name().equalsIgnoreCase(contentType)) | ||
.findFirst() | ||
.orElseThrow(() -> new IllegalArgumentException("Feed 내 해당 컨텐츠 종류(" + contentType + ")는 지원하지 않습니다.")); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package ddingdong.ddingdongBE.domain.feed.service; | ||
|
||
import ddingdong.ddingdongBE.domain.feed.service.dto.command.CreateFeedCommand; | ||
|
||
public interface FacadeClubFeedService { | ||
|
||
void create(CreateFeedCommand command); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package ddingdong.ddingdongBE.domain.feed.service; | ||
|
||
import ddingdong.ddingdongBE.domain.club.entity.Club; | ||
import ddingdong.ddingdongBE.domain.club.service.ClubService; | ||
import ddingdong.ddingdongBE.domain.feed.entity.Feed; | ||
import ddingdong.ddingdongBE.domain.feed.service.dto.command.CreateFeedCommand; | ||
import ddingdong.ddingdongBE.domain.filemetadata.entity.DomainType; | ||
import ddingdong.ddingdongBE.domain.filemetadata.service.FileMetaDataService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class FacadeClubFeedServiceImpl implements FacadeClubFeedService{ | ||
|
||
private final ClubService clubService; | ||
private final FileMetaDataService fileMetaDataService; | ||
private final FeedService feedService; | ||
|
||
@Override | ||
@Transactional | ||
public void create(CreateFeedCommand command) { | ||
Club club = clubService.getByUserId(command.user().getId()); | ||
Feed feed = command.toEntity(club); | ||
Long createdId = feedService.create(feed); | ||
|
||
if (feed.isImage()) { | ||
fileMetaDataService.updateStatusToCoupled(command.mediaId(), DomainType.FEED_IMAGE, createdId); | ||
} | ||
|
||
if (feed.isVideo()) { | ||
fileMetaDataService.updateStatusToCoupled(command.mediaId(), DomainType.FEED_VIDEO, createdId); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,25 +19,25 @@ | |
@Transactional(readOnly = true) | ||
public class FacadeFeedService { | ||
|
||
private final GeneralFeedService generalFeedService; | ||
private final FeedService feedService; | ||
private final FileInformationService fileInformationService; | ||
|
||
public List<FeedListQuery> getAllByClubId(Long clubId) { | ||
List<Feed> feeds = generalFeedService.getAllByClubId(clubId); | ||
List<Feed> feeds = feedService.getAllByClubId(clubId); | ||
return feeds.stream() | ||
.map(FeedListQuery::from) | ||
.toList(); | ||
} | ||
|
||
public List<FeedListQuery> getNewestAll() { | ||
List<Feed> feeds = generalFeedService.getNewestAll(); | ||
List<Feed> feeds = feedService.getNewestAll(); | ||
return feeds.stream() | ||
.map(FeedListQuery::from) | ||
.toList(); | ||
} | ||
|
||
public FeedQuery getById(Long feedId) { | ||
Feed feed = generalFeedService.getById(feedId); | ||
Feed feed = feedService.getById(feedId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 단일 Feed 조회 시 존재하지 않는 자원에 대한 예외 처리 |
||
ClubProfileQuery clubProfileQuery = extractClubInfo(feed.getClub()); | ||
return FeedQuery.of(feed, clubProfileQuery); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package ddingdong.ddingdongBE.domain.feed.service; | ||
|
||
import ddingdong.ddingdongBE.domain.feed.entity.Feed; | ||
import java.util.List; | ||
|
||
public interface FeedService { | ||
|
||
List<Feed> getAllByClubId(Long clubId); | ||
|
||
List<Feed> getNewestAll(); | ||
|
||
Feed getById(Long feedId); | ||
|
||
Long create(Feed feed); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package ddingdong.ddingdongBE.domain.feed.service.dto.command; | ||
|
||
import ddingdong.ddingdongBE.domain.club.entity.Club; | ||
import ddingdong.ddingdongBE.domain.feed.entity.Feed; | ||
import ddingdong.ddingdongBE.domain.feed.entity.FeedType; | ||
import ddingdong.ddingdongBE.domain.user.entity.User; | ||
import lombok.Builder; | ||
|
||
@Builder | ||
public record CreateFeedCommand( | ||
String activityContent, | ||
String mediaId, | ||
String contentType, | ||
User user | ||
) { | ||
|
||
public Feed toEntity(Club club) { | ||
return Feed.builder() | ||
.activityContent(activityContent) | ||
.club(club) | ||
.feedType(FeedType.findByContentType(contentType)) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,6 @@ public enum DomainType { | |
ACTIVITY_REPORT_IMAGE, | ||
BANNER_WEB_IMAGE, | ||
BANNER_MOBILE_IMAGE, | ||
FEED_IMAGE, | ||
FEED_VIDEO | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -32,6 +32,13 @@ public UUID create(FileMetaData fileMetaData) { | |||||||||||||||||||||||||
return savedFileMetaData.getId(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||
public FileMetaData getById(String id) { | ||||||||||||||||||||||||||
UUID uuid = UUID.fromString(id); | ||||||||||||||||||||||||||
return fileMetaDataRepository.findById(uuid) | ||||||||||||||||||||||||||
.orElseThrow(() -> new ResourceNotFound("해당 FileMetaData(id: " + uuid + ")를 찾을 수 없습니다.)")); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Comment on lines
+35
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오탈자 수정이 필요합니다. - .orElseThrow(() -> new ResourceNotFound("해당 FileMetaData(id: " + uuid + ")를 찾을 수 없습니다.)"));
+ .orElseThrow(() -> new ResourceNotFound("해당 FileMetaData(id: " + uuid + ")를 찾을 수 없습니다.")); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||
public List<FileMetaData> getCoupledAllByDomainTypeAndEntityId(DomainType domainType, Long entityId) { | ||||||||||||||||||||||||||
return fileMetaDataRepository.findAllByDomainTypeAndEntityIdWithFileStatus(domainType, entityId, COUPLED); | ||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
ALTER TABLE feed | ||
DROP COLUMN file_url; | ||
|
||
ALTER TABLE feed | ||
DROP COLUMN thumbnail_url; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
하나의 피드가 동시에 이미지와 비디오로 처리될 가능성에 대한 고려
feed.isImage()
와feed.isVideo()
가 모두true
가 될 수 있는 상황이라면,fileMetaDataService.updateStatusToCoupled()
가 두 번 호출될 소지가 있습니다.실업무 로직상 ‘이미지 또는 비디오 중 하나’만 가능하다면
else if
로 제어하여 중복 처리를 회피하는 방안을 고려해 주세요.