Skip to content

Commit

Permalink
Feat/#29: 노래 등록 조회, 파트 등록, 킬링파트 조회 기능 구현 (#56)
Browse files Browse the repository at this point in the history
* feat: 노래 등록 조회, 파트 등록, 킬링파트 조회 기능 구현

* refactor: 테스트 인자 값 변경

이미지URL 에서 비디오 URL 로 변경
때 오타 수정

* refactor: RequiredArgsConstructor 로 변경, propagation 설정 제거

* refactor: 정적 팩토리 메서드명 변경

notPersisted 에서 forSave
persisted 에서 saved

* refactor: 부모 예외 명시

* refactor: 같은 길이의 동일한 시작 파트 검증 로직 변경

* refactor: request validation 어노테이션 변경

* refactor: 부정연산자 제거를 위한 메서드 변경

* refactor: 확장성을 위해 정렬된 상태를 가지지 않게 변경

킬링파트의 최대 갯수 상수 분리

* refactor: Song 에 Parts 의존성 추가

* refactor: 정확한 명시를 위해 Repository 어노테이션 추가

* fix: Integer 에 붙어있는 @notblank 어노테이션 수정

* refactor: 불변 필드에 final 키워드 추가

* refactor: Part 중복 검증 로직 추가

* refactor: JPA 를 사용하는 테스트를 위한 추상 클래스 추가, DisplayName 수정
  • Loading branch information
splitCoding authored Jul 17, 2023
1 parent 628efb5 commit 4a2337f
Show file tree
Hide file tree
Showing 39 changed files with 2,055 additions and 79 deletions.
38 changes: 22 additions & 16 deletions backend/build.gradle
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.1'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'org.springframework.boot' version '3.1.1'
id 'io.spring.dependency-management' version '1.1.0'
}

group = 'shook'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
sourceCompatibility = '17'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'

compileOnly 'org.projectlombok:lombok'

runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'

annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
7 changes: 3 additions & 4 deletions backend/src/main/java/shook/shook/ShookApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
@SpringBootApplication
public class ShookApplication {

public static void main(String[] args) {
SpringApplication.run(ShookApplication.class, args);
}

public static void main(String[] args) {
SpringApplication.run(ShookApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package shook.shook.part.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shook.shook.part.application.dto.PartRegisterRequest;
import shook.shook.part.domain.Part;
import shook.shook.part.domain.PartLength;
import shook.shook.part.domain.Vote;
import shook.shook.part.domain.repository.PartRepository;
import shook.shook.part.domain.repository.VoteRepository;
import shook.shook.part.exception.PartException;
import shook.shook.song.domain.Song;
import shook.shook.song.domain.repository.SongRepository;
import shook.shook.song.exception.SongException;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class PartService {

private final SongRepository songRepository;
private final PartRepository partRepository;
private final VoteRepository voteRepository;

@Transactional
public void register(final PartRegisterRequest request) {
final Song song = songRepository.findById(request.getSongId())
.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;
}
voteToExistPart(song, part);
}

private void 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);
}

private void 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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package shook.shook.part.application.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

@NotNull
@PositiveOrZero
private Integer startSecond;

@NotNull
@Positive
private Integer length;

@NotNull
@Positive
private Long songId;
}
99 changes: 83 additions & 16 deletions backend/src/main/java/shook/shook/part/domain/Part.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
Expand All @@ -13,6 +12,7 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.HashSet;
Expand All @@ -21,46 +21,113 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import shook.shook.vote.domain.Vote;
import shook.shook.part.exception.PartException;
import shook.shook.part.exception.VoteException;
import shook.shook.song.domain.Song;

@Entity
@Table(name = "part")
@EntityListeners(AuditingEntityListener.class)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "part")
@Entity
public class Part {

private static final int MINIMUM_START = 0;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
@Column(nullable = false, updatable = false)
private int startSecond;

@Column(nullable = false)
@Column(nullable = false, updatable = false)
@Enumerated(EnumType.STRING)
private PartLength length;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "song_id", foreignKey = @ForeignKey(name = "none"))
@JoinColumn(name = "song_id", foreignKey = @ForeignKey(name = "none"), updatable = false)
@Getter(AccessLevel.NONE)
private Song song;

@OneToMany(mappedBy = "part")
@Getter(AccessLevel.NONE)
private Set<Vote> votes = new HashSet<>();
private final Set<Vote> votes = new HashSet<>();

@Column(nullable = false, updatable = false)
@CreatedDate
private LocalDateTime createdAt;
private LocalDateTime createdAt = LocalDateTime.now();

@PrePersist
private void prePersist() {
createdAt = LocalDateTime.now();
}

public Part(final Song song) {
private Part(final Long id, final int startSecond, final PartLength length, final Song song) {
this.id = id;
this.startSecond = startSecond;
this.length = length;
this.song = song;
}

public static Part saved(
final Long id,
final int startSecond,
final PartLength length,
final Song song
) {
validateStartSecond(startSecond, length, song.getLength());
return new Part(id, startSecond, length, song);
}

private static void validateStartSecond(
final int startSecond,
final PartLength partLength,
final int songLength
) {
if (startSecond < MINIMUM_START) {
throw new PartException.StartLessThanZeroException();
}
if (songLength <= startSecond) {
throw new PartException.StartOverSongLengthException();
}
if (songLength < partLength.getEndSecond(startSecond)) {
throw new PartException.EndOverSongLengthException();
}
}

public static Part forSave(
final int startSecond,
final PartLength length,
final Song song
) {
validateStartSecond(startSecond, length, song.getLength());
return new Part(null, startSecond, length, song);
}

public void vote(final Vote vote) {
validateVote(vote);
this.votes.add(vote);
}

private void validateVote(final Vote vote) {
if (vote.isBelongToOtherPart(this)) {
throw new VoteException.VoteForOtherPartException();
}
if (votes.contains(vote)) {
throw new VoteException.DuplicateVoteExistException();
}
}

public boolean hasEqualStartAndLength(final Part other) {
return this.startSecond == other.startSecond && this.length.equals(other.length);
}

public boolean isBelongToOtherSong(final Song song) {
return !song.equals(this.song);
}

public Set<Vote> getVotes() {
return new HashSet<>(votes);
}

public int getVoteCount() {
return votes.size();
}
Expand Down
14 changes: 14 additions & 0 deletions backend/src/main/java/shook/shook/part/domain/PartLength.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package shook.shook.part.domain;

import java.util.Arrays;
import shook.shook.part.exception.PartException;

public enum PartLength {
SHORT(5),
STANDARD(10),
Expand All @@ -11,6 +14,17 @@ public enum PartLength {
this.value = value;
}

public static PartLength findBySecond(final int second) {
return Arrays.stream(values())
.filter(length -> length.value == second)
.findFirst()
.orElseThrow(PartException.InvalidLengthException::new);
}

public int getEndSecond(final int start) {
return start + this.value;
}

public int getValue() {
return value;
}
Expand Down
Loading

0 comments on commit 4a2337f

Please sign in to comment.