Skip to content
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

[FEAT] 플레이리스트 API #60

Merged
merged 47 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8414fd4
feat: #44 기본 클래스 생성
dgh06175 Sep 11, 2024
6e24f69
feat: #44 기본 클래스 생성 및 스프링 빈 연결
dgh06175 Sep 11, 2024
f0981c7
feat: #44 1차 컨트롤러, 서비스, dto 구현
dgh06175 Sep 11, 2024
712b992
feat: #44 2차 컨트롤러, 서비스, dto 구현
dgh06175 Sep 11, 2024
420f6e3
refactor: #44 매개변수 수정
dgh06175 Sep 11, 2024
e3dd257
feat: #44 플레이리스트 엔티티에 업로더를 의미하는 멤버 필드 추가
dgh06175 Sep 18, 2024
7625481
test: 트랙 테스트 메소드 이름을 서비스 메소드 네이밍과 동일하게 직관적으로 변경
dgh06175 Sep 18, 2024
ffa1b22
feat: #44 플레이리스트 안의 트랙 정보를 가지고 있는 PlaylistTrack 엔티티의 레파지토리 추가
dgh06175 Sep 18, 2024
bf8c193
test: #44 실패하는 테스트 작성
dgh06175 Sep 19, 2024
fa680fb
test: #44 테스트 클래스 네이밍 오타 수정, TrackServiceTest 일부 메소드 재귀 값 비교로 수정
dgh06175 Sep 19, 2024
c5eda3f
test: #44 테스트 코드 중복 코드 제거
dgh06175 Sep 19, 2024
00d0bdf
feat: #44 PlaylistTrack BaseEntity 상속
dgh06175 Sep 19, 2024
80825e8
feat: #44 플레이리스트 생성
dgh06175 Sep 20, 2024
c1b35a6
feat: #44 플레이리스트 목록 가져오기
dgh06175 Sep 21, 2024
1f39930
feat: #44 플레이리스트 목록 검색
dgh06175 Sep 21, 2024
c8872ca
test: #44 플레이리스트 임시 데이터 추가 및 페이징, 검색 등 테스트 세분화하여 추가
dgh06175 Sep 21, 2024
59fffee
feat: #44 플레이리스트 에러코드 작성
dgh06175 Sep 21, 2024
5f37e2b
feat: 멤버 아이디와 clientId 가 동일하면 동일하다고 취급
dgh06175 Sep 21, 2024
38b2a36
feat: 트랙 삭제시 비교 로직에서 멤버 동일한지 비교할떄 id 비교가 아닌 Member 객체 비교로 변환
dgh06175 Sep 21, 2024
6cbae50
feat: #44 플레이리스트 에러 코드 작성
dgh06175 Sep 21, 2024
0167ac7
test: #44 삭제 테스트 작성
dgh06175 Sep 21, 2024
d3331fb
feat: #44 플레이리스트 삭제
dgh06175 Sep 21, 2024
7970e3a
feat: #44 플레이리스트 디테일 조회
dgh06175 Sep 21, 2024
67bf465
test: #44 테스트 잘못 작성한것 수정
dgh06175 Sep 21, 2024
c11c131
feat: #44 플레이리스트에 트랙 추가
dgh06175 Sep 21, 2024
3fb064c
test: 테스트 데이터 추가에 따른 테스트 코드 일부 수정:
dgh06175 Sep 21, 2024
022fcbf
chore: #44 playlist 엔드포인트 접근 허용
dgh06175 Sep 21, 2024
072a7db
feat: #44 어노테이션 추가
dgh06175 Sep 21, 2024
6a25b6b
docs: #44 파라미터 기본값 및 설명 추가
dgh06175 Sep 22, 2024
8064806
docs: #44 파라미터 기본값 및 설명 추가
dgh06175 Sep 22, 2024
26c05d7
feat: #44 플레이리스트 제목과 이미지 수정 API
dgh06175 Sep 26, 2024
57a7906
feat: #44 플레이리스트에 트랙 추가 시 중복 트랙 검사
dgh06175 Sep 26, 2024
ffe8863
feat: #44 플레이리스트 제목 및 이미지 수정
dgh06175 Sep 26, 2024
7bb7774
feat: #44 플레이리스트 트랙 순서 변경, 플레이리스트 트랙 삭제 API 컨트롤러 작성
dgh06175 Sep 26, 2024
05a2bae
test: #44 빈 플레이리스트 테스트 케이스 추가 및 기존 테스트 코드 알맞게 수정
dgh06175 Sep 26, 2024
9f833f0
test: #44 플레이리스트에 트랙 추가 테스트
dgh06175 Sep 26, 2024
26d5a9b
test: #44 플레이리스트 트랙 순서 변경 테스트 작성
dgh06175 Sep 26, 2024
5e0728f
test: #44 플레이리스트 트랙 삭제 테스트 작성
dgh06175 Sep 26, 2024
d0d8453
refactor: #44 플레이리스트 권한 검증 메소드 추출
dgh06175 Sep 26, 2024
9b959b1
feat: #44 플레이리스트 엔티티 playlistTracks List -> Set 으로 변경
dgh06175 Sep 26, 2024
a5f7aef
feat: #44 플레이리스트 업데이트
dgh06175 Sep 26, 2024
76b29ba
feat: #44 플레이리스트 삭제
dgh06175 Sep 26, 2024
0746c45
feat: #44 에러 메세지 정리
dgh06175 Sep 26, 2024
f594a8a
refactor: #44 검증 메소드 추출 및 정리
dgh06175 Sep 26, 2024
1b9ac94
refactor: 공백 수정
dgh06175 Sep 26, 2024
d5ac4ca
feat: #44 트랙 정렬해서 반환하기
dgh06175 Sep 26, 2024
3f27c9f
feat: #44 플레이리스트 제목 검색 기능 성능 개선
dgh06175 Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/com/cabin/plat/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/members/**").authenticated()
.requestMatchers("/tracks/**").authenticated()
.requestMatchers("/address/**").authenticated()
.requestMatchers("/playlists/**").authenticated()
.requestMatchers("images/**").authenticated()
.anyRequest().denyAll());

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/cabin/plat/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.cabin.plat.global.common.BaseEntity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
Expand Down Expand Up @@ -52,4 +53,17 @@ public class Member extends BaseEntity {
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private SocialType socialType;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Member)) return false;
Member member = (Member) o;
return id.equals(member.id) && clientId.equals(member.clientId);
}

@Override
public int hashCode() {
return Objects.hash(id, clientId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.cabin.plat.domain.playlist.controller;

import com.cabin.plat.config.AuthMember;
import com.cabin.plat.domain.member.entity.Member;
import com.cabin.plat.domain.playlist.dto.PlaylistRequest;
import com.cabin.plat.domain.playlist.dto.PlaylistResponse;
import com.cabin.plat.domain.playlist.service.PlaylistService;
import com.cabin.plat.global.common.BaseResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/playlists")
@Tag(name = "플레이리스트 API", description = "플레이리스트 관련 API 입니다.")
public class PlaylistController {

private final PlaylistService playlistService;

@Operation(summary = "플레이리스트 생성", description = "플레이리스트를 생성한다.")
@PostMapping
public BaseResponse<PlaylistResponse.PlayListId> addPlaylist(
@AuthMember Member member,
@RequestBody PlaylistRequest.PlaylistUpload playlistUpload) {

return BaseResponse.onSuccess(playlistService.addPlaylist(member, playlistUpload));
}

@Operation(summary = "플레이리스트 목록 조회", description = "사용자의 모든 플레이리스트를 가져온다. 페이지네이션을 지원합니다. page 파라미터에 페이지 번호를 입력해주세요.")
@GetMapping
public BaseResponse<PlaylistResponse.Playlists> getPlaylists(
@AuthMember Member member,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {

return BaseResponse.onSuccess(playlistService.getPlaylists(member, page, size));
}

@Operation(summary = "플레이리스트 목록 검색", description = "사용자의 플레이리스트를 제목으로 검색해서 가져온다. 페이지네이션을 지원합니다. page 파라미터에 페이지 번호를 입력해주세요.")
@GetMapping("/search")
public BaseResponse<PlaylistResponse.Playlists> getPlaylists(
@AuthMember Member member,
@RequestParam(defaultValue = "") String title,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {

return BaseResponse.onSuccess(playlistService.getSearchedPlaylists(member, title, page, size));
}

@Operation(summary = "플레이리스트 삭제", description = "플레이리스트를 삭제한다.")
@DeleteMapping("/{playlistId}")
public BaseResponse<PlaylistResponse.PlayListId> deletePlaylist(
@AuthMember Member member,
@PathVariable("playlistId") Long playlistId) {

return BaseResponse.onSuccess(playlistService.deletePlaylist(member, playlistId));
}

@Operation(summary = "플레이리스트 디테일 조회", description = "사용자의 플레이리스트 하나의 정보를 가져온다. 내부 트랙의 정보도 모두 가져온다.")
@GetMapping("/{playlistId}/detail")
public BaseResponse<PlaylistResponse.PlaylistDetail> getPlaylistDetail(
@AuthMember Member member,
@PathVariable("playlistId") Long playlistId) {

return BaseResponse.onSuccess(playlistService.getPlaylistDetail(member, playlistId));
}

@Operation(summary = "플레이리스트 정보 수정", description = "플레이리스트 사진과 제목을 편집한다.")
@PatchMapping("/{playlistId}")
public BaseResponse<PlaylistResponse.PlayListId> updatePlaylist(
@AuthMember Member member,
@PathVariable("playlistId") Long playlistId,
@RequestBody PlaylistRequest.PlaylistEdit playlistEdit) {

return BaseResponse.onSuccess(playlistService.updatePlaylistTitleAndImage(member, playlistId, playlistEdit));
}

@Operation(summary = "플레이리스트에 트랙 추가", description = "플레이리스트에 트랙을 추가한다.")
@PostMapping("/{playlistId}")
public BaseResponse<PlaylistResponse.PlayListId> addTrackToPlaylist(
@AuthMember Member member,
@PathVariable("playlistId") Long playlistId,
@RequestBody PlaylistRequest.TrackId trackId) {

return BaseResponse.onSuccess(playlistService.addTrackToPlaylist(member, playlistId, trackId));
}

@Operation(summary = "플레이리스트 트랙 순서 변경", description = "플레이리스트 내부의 트랙의 순서를 변경한다.")
@PatchMapping("/{playlistId}/tracks/order")
public BaseResponse<PlaylistResponse.PlayListId> updateTrackOrders(
@AuthMember Member member,
@PathVariable("playlistId") Long playlistId,
@RequestBody PlaylistRequest.PlaylistOrders playlistOrders) {

return BaseResponse.onSuccess(playlistService.updateTrackOrders(member, playlistId, playlistOrders));
}

@Operation(summary = "플레이리스트에서 트랙 삭제", description = "플레이리스트 내부의 트랙을 삭제합니다.")
@DeleteMapping("/{playlistId}/tracks/{trackId}")
public BaseResponse<PlaylistResponse.PlayListId> deleteTrackFromPlaylist(
@AuthMember Member member,
@PathVariable("playlistId") Long playlistId,
@PathVariable("trackId") Long trackId) {

return BaseResponse.onSuccess(playlistService.deleteTrackFromPlaylist(member, playlistId, trackId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.cabin.plat.domain.playlist.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.*;

public class PlaylistRequest {

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PlaylistEdit {
@Schema(description = "플레이리스트 제목", example = "플레이리스트 제목")
private String title;

@Schema(description = "플레이리스트 이미지 URL", example = "https://s3.amazonaws.com/mybucket/images/sample.jpg")
private String playlistImageUrl;
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PlaylistOrders {
private List<TrackOrder> tracks;
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PlaylistUpload {
@Schema(description = "플레이리스트 제목", example = "플레이리스트 제목")
private String title;

@Schema(description = "플레이리스트 이미지 URL", example = "https://s3.amazonaws.com/mybucket/images/sample.jpg")
private String playlistImageUrl;

private List<TrackOrder> tracks;
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TrackOrder {
@Schema(description = "트랙 고유 ID", example = "1")
private Long trackId;

@Schema(description = "플레이리스트에서 트랙의 순서", example = "0")
private int orderIndex;
}

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class TrackId {
@Schema(description = "트랙 고유 ID", example = "1")
private Long trackId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.cabin.plat.domain.playlist.dto;

import com.cabin.plat.domain.track.dto.TrackResponse.TrackDetail;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import lombok.*;

public class PlaylistResponse {
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PlayListId {
@Schema(description = "플레이리스트 고유 ID", example = "1")
private Long playlistId;
}

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Playlists {
private List<PlaylistInfo> playlists;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PlaylistInfo {
@Schema(description = "플레이리스트 고유 ID", example = "1")
private Long playlistId;

@Schema(description = "플레이리스트 제목", example = "플레이리스트 제목")
private String title;

@Schema(description = "플레이리스트 이미지 URL", example = "https://s3.amazonaws.com/mybucket/images/sample.jpg")
private String playlistImageUrl;

@Schema(description = "플레이리스트 생성일", example = "2024-09-22T07:23:09.102Z")
private LocalDateTime createdAt;

private Set<String> uploaderNicknames;
}
}

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PlaylistDetail {
@Schema(description = "플레이리스트 고유 ID", example = "1")
private Long playlistId;

@Schema(description = "플레이리스트 제목", example = "플레이리스트 제목")
private String title;

@Schema(description = "플레이리스트 이미지 URL", example = "https://s3.amazonaws.com/mybucket/images/sample.jpg")
private String playlistImageUrl;

@Schema(description = "플레이리스트 생성일", example = "2024-09-22T07:23:09.102Z")
private LocalDateTime createdAt;

private List<TrackDetailOrder> tracks;
}

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class TrackDetailOrder {
@Schema(description = "트랙 순서", example = "1")
private int orderIndex;

@Schema(description = "트랙 상세 정보")
private TrackDetail trackDetail;
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/cabin/plat/domain/playlist/entity/Playlist.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.cabin.plat.domain.playlist.entity;

import com.cabin.plat.domain.member.entity.Member;
import com.cabin.plat.global.common.BaseEntity;
import jakarta.persistence.*;
import java.util.Set;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Getter
@Builder
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@SQLRestriction("deleted_at is null")
public class Playlist extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "playlist_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@Column(nullable = false)
private String title;

private String playlistImageUrl;

@OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL)
private Set<PlaylistTrack> playlistTracks;

public void updatePlaylist(String title, String playlistImageUrl) {
this.title = title;
this.playlistImageUrl = playlistImageUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.cabin.plat.domain.playlist.entity;

import com.cabin.plat.domain.track.entity.Track;
import com.cabin.plat.global.common.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Getter
@Builder
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@SQLRestriction("deleted_at is null")
public class PlaylistTrack extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "track_playlist_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "playlist_id", nullable = false)
private Playlist playlist;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "track_id", nullable = false)
private Track track;

@Setter
@Column(name = "order_index", nullable = false)
private int orderIndex;
}
Loading
Loading