Skip to content

Commit

Permalink
Feat/#87: 파트 등록, 노래 정보 조회 API 구현 (#88)
Browse files Browse the repository at this point in the history
* fix: id를 포함한 객체를 HashSet 에 저장하게 수정

* config: RestAssured 의존성 추가

* feat: 파트 등록 API 구현

* fix: 더미 데이터를 이용한 테스트를 위한 테스트 파라미터 변경

* feat: 노래 정보 조회 API 구현

* fix: Set 자료구조 List 로 변경

정확한 해시값을 위한 선행되야하는 영속화 과정에서 LazyLoading 이 발생시 컬렉션에 직접 넣지 않았음에도 들어가있는 상태를 가지게 되는 문제점

* test: 파트 등록 테스트 추가
  • Loading branch information
splitCoding authored Jul 19, 2023
1 parent 57c61ed commit f605c32
Show file tree
Hide file tree
Showing 19 changed files with 444 additions and 63 deletions.
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
}

tasks.named('test') {
Expand Down
32 changes: 19 additions & 13 deletions backend/src/main/java/shook/shook/part/application/PartService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shook.shook.part.application.dto.PartRegisterRequest;
import shook.shook.part.application.dto.PartRegisterResponse;
import shook.shook.part.domain.Part;
import shook.shook.part.domain.PartLength;
import shook.shook.part.domain.Vote;
Expand All @@ -24,37 +25,42 @@ public class PartService {
private final VoteRepository voteRepository;

@Transactional
public void register(final PartRegisterRequest request) {
final Song song = songRepository.findById(request.getSongId())
public PartRegisterResponse register(final Long songId, final PartRegisterRequest request) {
final Song song = songRepository.findById(songId)
.orElseThrow(SongException.SongNotExistException::new);

final int startSecond = request.getStartSecond();
final PartLength partLength = PartLength.findBySecond(request.getLength());
final Part part = Part.forSave(startSecond, partLength, song);

if (song.isUniquePart(part)) {
addPartAndVote(song, part);
return;
return addPartAndVote(song, part);
}
voteToExistPart(song, part);

return voteToExistPart(song, part);
}

private void addPartAndVote(final Song song, final Part part) {
private PartRegisterResponse addPartAndVote(final Song song, final Part part) {
song.addPart(part);
partRepository.save(part);

final Vote newVote = Vote.forSave(part);
part.vote(newVote);
voteRepository.save(newVote);
voteToPart(part);

return PartRegisterResponse.of(song, part);
}

private void voteToExistPart(final Song song, final Part part) {
private PartRegisterResponse voteToExistPart(final Song song, final Part part) {
final Part existPart = song.getSameLengthPartStartAt(part)
.orElseThrow(PartException.PartNotExistException::new);

final Vote newVote = Vote.forSave(existPart);
existPart.vote(newVote);
voteRepository.save(newVote);
voteToPart(existPart);

return PartRegisterResponse.of(song, existPart);
}

private void voteToPart(final Part part) {
final Vote newVote = Vote.forSave(part);
part.vote(newVote);
voteRepository.save(newVote);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,4 @@ public class PartRegisterRequest {
@NotNull
@Positive
private Integer length;

@NotNull
@Positive
private Long songId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package shook.shook.part.application.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import shook.shook.part.domain.Part;
import shook.shook.song.domain.Song;

@AllArgsConstructor
@Getter
public class PartRegisterResponse {

private final int rank;
private final String partUrl;

public static PartRegisterResponse of(final Song song, final Part part) {
return new PartRegisterResponse(
song.getRank(part),
song.getPartVideoUrl(part)
);
}
}
20 changes: 15 additions & 5 deletions backend/src/main/java/shook/shook/part/domain/Part.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -33,6 +33,7 @@
public class Part {

private static final int MINIMUM_START = 0;
private static final String EMBED_LINK_PATH_PARAMETER_FORMAT = "?start=%d&end=%d";

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -51,7 +52,7 @@ public class Part {
private Song song;

@OneToMany(mappedBy = "part")
private final Set<Vote> votes = new HashSet<>();
private final List<Vote> votes = new ArrayList<>();

@Column(nullable = false, updatable = false)
private LocalDateTime createdAt = LocalDateTime.now();
Expand Down Expand Up @@ -125,8 +126,17 @@ public boolean isBelongToOtherSong(final Song song) {
return !song.equals(this.song);
}

public Set<Vote> getVotes() {
return new HashSet<>(votes);
//TODO: 반환하는 형태가 변할 가능성 있어 리팩토링 대상
public String getStartAndEndUrlPathParameter() {
return String.format(EMBED_LINK_PATH_PARAMETER_FORMAT, startSecond, getEndSecond());
}

public int getEndSecond() {
return length.getEndSecond(startSecond);
}

public List<Vote> getVotes() {
return new ArrayList<>(votes);
}

public int getVoteCount() {
Expand Down
31 changes: 31 additions & 0 deletions backend/src/main/java/shook/shook/part/ui/PartController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package shook.shook.part.ui;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
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.RestController;
import shook.shook.part.application.PartService;
import shook.shook.part.application.dto.PartRegisterRequest;
import shook.shook.part.application.dto.PartRegisterResponse;

@RequiredArgsConstructor
@RequestMapping("/songs/{song_id}/parts")
@RestController
public class PartController {

private final PartService partService;

@PostMapping
public ResponseEntity<PartRegisterResponse> registerPart(
@PathVariable(name = "song_id") final Long songId,
@RequestBody final PartRegisterRequest request
) {
final PartRegisterResponse register = partService.register(songId, request);

return ResponseEntity.status(HttpStatus.CREATED).body(register);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public KillingPartResponse showTopKillingPart(final Long songId) {
.orElseThrow(SongException.SongNotExistException::new);

return song.getTopKillingPart()
.map(KillingPartResponse::from)
.map((killingPart) -> KillingPartResponse.of(song, killingPart))
.orElseGet(KillingPartResponse::empty);
}

Expand All @@ -55,6 +55,6 @@ public KillingPartsResponse showKillingParts(final Long songId) {

final List<Part> killingParts = song.getKillingParts();

return KillingPartsResponse.of(killingParts);
return KillingPartsResponse.of(song, killingParts);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,32 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import shook.shook.part.domain.Part;
import shook.shook.part.domain.PartLength;
import shook.shook.song.domain.Song;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class KillingPartResponse {

private final boolean exist;
private final Integer rank;
private final Integer start;
private final Integer end;
private final String videoUrl;

public static KillingPartResponse from(final Part part) {
public static KillingPartResponse of(final Song song, final Part part) {
final int startSecond = part.getStartSecond();
final PartLength length = part.getLength();
return new KillingPartResponse(true, startSecond, length.getEndSecond(startSecond));
}

public static KillingPartResponse of(final Integer start, final Integer end) {
return new KillingPartResponse(true, start, end);
final int rank = song.getRank(part);
final String partVideoUrl = song.getPartVideoUrl(part);
return new KillingPartResponse(
true,
rank,
startSecond,
part.getEndSecond(),
partVideoUrl
);
}

public static KillingPartResponse empty() {
return new KillingPartResponse(false, null, null);
return new KillingPartResponse(false, null, null, null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import shook.shook.part.domain.Part;
import shook.shook.song.domain.Song;

@AllArgsConstructor
@Getter
public class KillingPartsResponse {

private final List<KillingPartResponse> responses;

public static KillingPartsResponse of(final List<Part> parts) {
final List<KillingPartResponse> killingPartResponses = parts.stream()
.map(KillingPartResponse::from)
public static KillingPartsResponse of(final Song song, final List<Part> parts) {
final List<KillingPartResponse> killingPartResponses = parts.stream().
map((killingPart) -> KillingPartResponse.of(song, killingPart))
.toList();

return new KillingPartsResponse(killingPartResponses);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package shook.shook.song.application.dto;

import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import shook.shook.song.domain.Song;
Expand All @@ -9,21 +9,23 @@
@Getter
public class SongResponse {

private Long id;
private String title;
private String videoUrl;
private String singer;
private int length;
private LocalDateTime createdAt;
private final Long id;
private final String title;
private final String singer;
private final int videoLength;
private final String videoUrl;
private final List<KillingPartResponse> killingParts;

public static SongResponse from(final Song song) {
return new SongResponse(
song.getId(),
song.getTitle(),
song.getVideoUrl(),
song.getSinger(),
song.getLength(),
song.getCreatedAt()
song.getVideoUrl(),
song.getKillingParts().stream()
.map((killingPart) -> KillingPartResponse.of(song, killingPart))
.toList()
);
}
}
12 changes: 9 additions & 3 deletions backend/src/main/java/shook/shook/song/domain/Parts.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -22,7 +20,7 @@ public class Parts {
private static final int KILLING_PART_COUNT = 3;

@OneToMany(mappedBy = "song")
private final Set<Part> parts = new HashSet<>();
private final List<Part> parts = new ArrayList<>();

public void addPart(final Part part) {
validatePart(part);
Expand Down Expand Up @@ -74,6 +72,14 @@ public Optional<Part> getSameLengthPartStartAt(final Part other) {
.findFirst();
}

public int getRank(final Part part) {
if (!parts.contains(part)) {
throw new PartException.PartNotExistException();
}

return sortedByVoteCountDescending().indexOf(part) + 1;
}

public List<Part> getParts() {
return new ArrayList<>(parts);
}
Expand Down
8 changes: 8 additions & 0 deletions backend/src/main/java/shook/shook/song/domain/Song.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ public List<Part> getKillingParts() {
return parts.getKillingParts();
}

public int getRank(final Part part) {
return parts.getRank(part);
}

public String getPartVideoUrl(final Part part) {
return videoUrl.getValue() + part.getStartAndEndUrlPathParameter();
}

public String getTitle() {
return title.getValue();
}
Expand Down
24 changes: 24 additions & 0 deletions backend/src/main/java/shook/shook/song/ui/SongController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package shook.shook.song.ui;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import shook.shook.song.application.SongService;
import shook.shook.song.application.dto.SongResponse;

@RequiredArgsConstructor
@RequestMapping("/songs/{song_id}")
@RestController
public class SongController {

private final SongService songService;

@GetMapping
public ResponseEntity<SongResponse> showSongById(@PathVariable(name = "song_id") Long songId) {
final SongResponse response = songService.findById(songId);
return ResponseEntity.ok(response);
}
}
Loading

0 comments on commit f605c32

Please sign in to comment.