Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class ArtistsController {
private final ArtistService artistService;
private final ArtistEnrichService enrichService;

@Operation(summary = "아티스트 저장", description = "임의의 가수 300명(or 팀)을 DB에 저장합니다.")
@Operation(summary = "아티스트 저장", description = "임의의 가수(or 팀)을 DB에 저장합니다.")
@GetMapping("/saved")
public RsData<Integer> saveArtist() {
int saved = artistService.setArtist();
Expand All @@ -43,7 +43,7 @@ public RsData<Integer> enrich(
public RsData<Void> create(
@RequestBody CreateRequest reqBody
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateReqeust 안에 Notnull이나 검증 어노테이션이 붙어있어서 Valid 어노테이션 붙여야 정상적으로 작동하실 것 같습니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 추가하겠습니다!!

) {
artistService.createArtist(reqBody.artistName(), reqBody.artistGroup(), reqBody.artistGroup(), reqBody.genreName());
artistService.createArtist(reqBody.artistName(), reqBody.artistGroup(), reqBody.artistType(), reqBody.genreName());
return RsData.success("아티스트 생성이 완료되었습니다.", null);
}

Expand Down Expand Up @@ -79,4 +79,53 @@ public RsData<Void> delete(
artistService.delete(id);
return RsData.success("아티스트 정보를 삭제했습니다.", null);
}

@Operation(summary = "아티스트 검색", description = "아티스트 이름을 입력받아 검색합니다.")
@PostMapping("/{id}")
public RsData<Void> search(
@PathVariable Long id
) {
return RsData.success("아티스트 검색에 성공했습니다.", null);
}

@Operation(summary = "아티스트 찜하기", description = "id 에 해당하는 특정 아티스트를 찜합니다.")
@PostMapping("/likes/{id}")
public void artistLikes(
@PathVariable Long id
) {}

@Operation(summary = "아티스트 찜 해체", description = "id 에 해당하는 아티스트에게 등록했던 찜을 해제합니다.")
@DeleteMapping("/likes/{id}")
public void deleteArtistLikes(
@PathVariable Long id
) {}

@Operation(summary = "개인화된 공연 리스트 생성", description = "유저가 찜한 아티스트를 기반으로 공연 리스트를 생성합니다.")
@PostMapping("/list")
public void concertList() {}

@Operation(summary = "아티스트 인기 순위", description = "Spotify 인기도를 바탕으로 아티스트 인기 순위 랭킹을 제공합니다.")
@GetMapping("/ranking")
public void artistRanking() {}

@Operation(summary = "장르 기반 아티스트 추천", description = "찜한 장르를 기반으로 아티스트 추천 리스트를 제공합니다.")
@GetMapping("/recommendation/{genreId}")
public void recommendArtist(
@PathVariable Long genreId
) {}

@Operation(summary = "공연 셋리스트 생성", description = "사용자가 공연 셋리스트를 생성합니다.")
@PostMapping("/setlist/{concertId}/{artistId}")
public void makeSetlist(
@PathVariable Long concertId,
@PathVariable Long artistId
) {}

@Operation(summary = "공연 셋리스트 조회", description = "다른 사용자들이 생성한 셋리스트를 조회합니다.")
@GetMapping("/setlist/{concertId}/{artistId}")
public void getSetlist(
@PathVariable Long concertId,
@PathVariable Long artistId
) {}
}

Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
package com.back.web7_9_codecrete_be.domain.artists.dto.request;

import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record CreateRequest(
@NotNull(message = "아티스트 이름은 필수로 입력해야합니다.")
@Size(max = 200, message = "아티스트 이름은 200자를 넘길 수 없습니다.")
String artistName,

@Size(max = 150, message = "아티스트 그룹 이름은 150자를 넘길 수 없습니다.")
String artistGroup,
String artistType,

@NotNull(message = "아티스트 타입은 필수로 입력해야합니다(SOLO or GROUP)")
ArtistType artistType,

@NotNull(message = "장르는 필수로 입력해야합니다.")
@Size(max = 30, message = "장르는 30자를 넘길 수 없습니다.")
String genreName
) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package com.back.web7_9_codecrete_be.domain.artists.dto.request;

import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;
import jakarta.validation.constraints.Size;

public record UpdateRequest(
@Size(max = 200, message = "아티스트 이름은 200자를 넘길 수 없습니다.")
String artistName,

@Size(max = 150, message = "아티스트 그룹 이름은 150자를 넘길 수 없습니다.")
String artistGroup,
String artistType,

ArtistType artistType,

@Size(max = 30, message = "장르 이름은 30자를 넘길 수 없습니다.")
String genreName
) {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.back.web7_9_codecrete_be.domain.artists.dto.response;

import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;

import java.util.List;

public record ArtistDetailResponse(
String artistName,
String artistGroup,
String artistType,
ArtistType artistType,
String profileImageUrl,
long likeCount,
int totalAlbums,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ public class Artist {
@Column(name = "artist_group")
private String artistGroup;

@Enumerated(EnumType.STRING)
@Column(name = "artist_type")
private String artistType;
private ArtistType artistType;

@ManyToOne(fetch = FetchType.LAZY)
private Genre genre;
Expand All @@ -36,22 +37,22 @@ public class Artist {
@Column(name = "name_ko", length = 200)
private String nameKo;

public Artist(String spotifyArtistId, String artistName, String artistGroup, String artistType, Genre genre) {
public Artist(String spotifyArtistId, String artistName, String artistGroup, ArtistType artistType, Genre genre) {
this.spotifyArtistId = spotifyArtistId;
this.artistName = artistName;
this.artistGroup = artistGroup; // 옵션 B: seed에서는 null
this.artistType = artistType; // 옵션 B: seed에서는 "SINGER"
this.genre = genre;
}

public Artist(String artistName, String artistGroup, String artistType, Genre genre) {
public Artist(String artistName, String artistGroup, ArtistType artistType, Genre genre) {
this.artistName = artistName;
this.artistGroup = artistGroup;
this.artistType = artistType;
this.genre = genre;
}

public void updateProfile(String nameKo, String artistGroup, String artistType) {
public void updateProfile(String nameKo, String artistGroup, ArtistType artistType) {
this.nameKo = nameKo;
this.artistGroup = artistGroup; // nullable
this.artistType = artistType; // "SOLO" / "GROUP"
Expand All @@ -65,7 +66,7 @@ public void changeGroup(String group) {
this.artistGroup = group;
}

public void changeType(String type) {
public void changeType(ArtistType type) {
this.artistType = type;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.back.web7_9_codecrete_be.domain.artists.entity;

public enum ArtistType {
SOLO,
GROUP
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.back.web7_9_codecrete_be.domain.artists.service;

import com.back.web7_9_codecrete_be.domain.artists.entity.Artist;
import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;
import com.back.web7_9_codecrete_be.domain.artists.repository.ArtistRepository;
import com.back.web7_9_codecrete_be.global.musicbrainz.MusicBrainzClient;
import com.back.web7_9_codecrete_be.global.wikidata.WikidataClient;
Expand Down Expand Up @@ -95,13 +96,28 @@ protected void enrichSingleArtist(Artist artist) {
}

// 기존 artistType이 있으면 유지, 없으면 가져온 값 사용
String artistType = result.artistType != null ? result.artistType : artist.getArtistType();
String artistTypeStr = result.artistType != null ? result.artistType :
(artist.getArtistType() != null ? artist.getArtistType().name() : null);

// String을 ArtistType enum으로 변환
ArtistType artistType;
if (artistTypeStr != null) {
try {
artistType = ArtistType.valueOf(artistTypeStr);
} catch (IllegalArgumentException e) {
log.warn("잘못된 artistType 값: {}, 기본값 SOLO 사용", artistTypeStr);
artistType = ArtistType.SOLO;
}
} else {
// 기존 값이 없고 새 값도 없으면 기본값 사용
artistType = artist.getArtistType() != null ? artist.getArtistType() : ArtistType.SOLO;
}

// 기존 row를 "보강"
// 기존 row를 "보강"
artist.updateProfile(result.nameKo, result.artistGroup, artistType);
// 명시적으로 save하여 변경사항을 DB에 즉시 반영
artistRepository.save(artist);
log.info("Enrich 성공: artistId={}, name={}, nameKo={}, group={}, type={}, source={}",
log.info("Enrich 성공: artistId={}, name={}, nameKo={}, group={}, type={}, source={}",
artist.getId(), artist.getArtistName(), result.nameKo,
result.artistGroup, artistType, result.source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.back.web7_9_codecrete_be.domain.artists.dto.response.ArtistListResponse;
import com.back.web7_9_codecrete_be.domain.artists.dto.response.ArtistDetailResponse;
import com.back.web7_9_codecrete_be.domain.artists.entity.Artist;
import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;
import com.back.web7_9_codecrete_be.domain.artists.entity.Genre;
import com.back.web7_9_codecrete_be.domain.artists.repository.ArtistRepository;
import com.back.web7_9_codecrete_be.domain.artists.repository.ArtistLikeRepository;
Expand All @@ -30,7 +31,7 @@ public int setArtist() {
}

@Transactional
public Artist createArtist(String artistName, String artistGroup, String artistType, String genreName) {
public Artist createArtist(String artistName, String artistGroup, ArtistType artistType, String genreName) {
Genre genre = genreService.findByGenreName(genreName);
if(artistRepository.existsByArtistName(artistName) || artistRepository.existsByNameKo(artistName)) {
throw new BusinessException(ArtistErrorCode.ARTIST_ALREADY_EXISTS);
Expand Down Expand Up @@ -85,8 +86,8 @@ public void updateArtist(Long id, UpdateRequest req) {
changed = true;
}

if (req.artistType() != null && !req.artistType().isBlank()) {
artist.changeType(req.artistType().trim());
if (req.artistType() != null) {
artist.changeType(req.artistType());
changed = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.back.web7_9_codecrete_be.domain.artists.dto.response.ArtistDetailResponse;
import com.back.web7_9_codecrete_be.domain.artists.dto.response.RelatedArtistResponse;
import com.back.web7_9_codecrete_be.domain.artists.dto.response.TopTrackResponse;
import com.back.web7_9_codecrete_be.domain.artists.entity.Artist;
import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;
import com.back.web7_9_codecrete_be.domain.artists.entity.Genre;
import com.back.web7_9_codecrete_be.domain.artists.repository.ArtistRepository;
import com.back.web7_9_codecrete_be.domain.artists.repository.GenreRepository;
Expand All @@ -19,7 +21,6 @@
import se.michaelthelin.spotify.enums.AlbumType;
import se.michaelthelin.spotify.exceptions.detailed.NotFoundException;
import se.michaelthelin.spotify.model_objects.specification.AlbumSimplified;
import se.michaelthelin.spotify.model_objects.specification.Artist;
import se.michaelthelin.spotify.model_objects.specification.Image;
import se.michaelthelin.spotify.model_objects.specification.Paging;
import se.michaelthelin.spotify.model_objects.specification.Track;
Expand Down Expand Up @@ -63,7 +64,7 @@ public int seedKoreanArtists300() {
int offset = 0;

while (totalSaved < targetCount) {
Paging<Artist> paging = api.searchArtists(q)
Paging<se.michaelthelin.spotify.model_objects.specification.Artist> paging = api.searchArtists(q)
.limit(limit)
.offset(offset)
.build()
Expand All @@ -85,10 +86,10 @@ public int seedKoreanArtists300() {
String mainGenreName = pickMainGenreName(spotifyArtist);
Genre genre = findOrCreateGenreByName(mainGenreName, null);

String artistType = inferArtistType(spotifyArtist);
String artistTypeStr = inferArtistType(spotifyArtist);
ArtistType artistType = ArtistType.valueOf(artistTypeStr);

com.back.web7_9_codecrete_be.domain.artists.entity.Artist artistEntity =
new com.back.web7_9_codecrete_be.domain.artists.entity.Artist(
Artist artistEntity = new Artist(
spotifyId,
name.trim(),
null, // artistGroup
Expand Down Expand Up @@ -127,7 +128,7 @@ public int seedKoreanArtists300() {
"korean indie", "korean rock"
);

private boolean isLikelyKoreanMusic(Artist a) {
private boolean isLikelyKoreanMusic(se.michaelthelin.spotify.model_objects.specification.Artist a) {
String[] genres = a.getGenres();
if (genres != null) {
for (String g : genres) {
Expand All @@ -142,7 +143,7 @@ private boolean isLikelyKoreanMusic(Artist a) {
return name != null && name.matches(".*[가-힣].*");
}

private String pickMainGenreName(Artist a) {
private String pickMainGenreName(se.michaelthelin.spotify.model_objects.specification.Artist a) {
String[] genres = a.getGenres();
if (genres == null || genres.length == 0) return "k-pop";

Expand All @@ -161,7 +162,7 @@ private String pickMainGenreName(Artist a) {
return "k-pop";
}

private String inferArtistType(Artist a) {
private String inferArtistType(se.michaelthelin.spotify.model_objects.specification.Artist a) {
String[] genres = a.getGenres();
if (genres != null) {
for (String g : genres) {
Expand All @@ -182,15 +183,15 @@ private Genre findOrCreateGenreByName(String genreName, String genreGroup) {
public ArtistDetailResponse getArtistDetail(
String spotifyArtistId,
String artistGroup,
String artistType,
ArtistType artistType,
long likeCount,
long artistId,
Long genreId
) {
try {
SpotifyApi api = spotifyClient.getAuthorizedApi();

Artist artist = api.getArtist(spotifyArtistId).build().execute(); // 메인 정보는 실패 시 예외 발생
se.michaelthelin.spotify.model_objects.specification.Artist artist = api.getArtist(spotifyArtistId).build().execute(); // 메인 정보는 실패 시 예외 발생

Track[] topTracks = safeGetTopTracks(api, spotifyArtistId);
Paging<AlbumSimplified> albums = safeGetAlbums(api, spotifyArtistId);
Expand All @@ -210,8 +211,8 @@ public ArtistDetailResponse getArtistDetail(
pickImageUrl(artist.getImages()),
likeCount,
albums != null ? albums.getTotal() : 0,
artist.getPopularity(), // (너가 별점으로 바꾸고 싶으면 여기 가공)
"", // Spotify에서는 설명을 제공하지 않아 공란 처리
artist.getPopularity(), // 별점으로 수정
"", // 설명
toAlbumResponses(albums != null ? albums.getItems() : null, spotifyArtistId),
toTopTrackResponses(topTracks),
relatedResponses
Expand Down Expand Up @@ -252,14 +253,9 @@ private Paging<AlbumSimplified> safeGetAlbums(SpotifyApi api, String artistId) {
}
}

/**
* ✅ related artists
* - 정상 호출
* - related가 비거나 404면 fallback(장르 기반 검색)으로 대체
*/
private List<RelatedArtistResponse> safeGetRelated(
SpotifyApi api,
Artist me,
se.michaelthelin.spotify.model_objects.specification.Artist me,
String artistGroup,
long artistId,
Long genreId
Expand All @@ -271,7 +267,7 @@ private List<RelatedArtistResponse> safeGetRelated(
}

try {
Artist[] related = api.getArtistsRelatedArtists(id).build().execute();
se.michaelthelin.spotify.model_objects.specification.Artist[] related = api.getArtistsRelatedArtists(id).build().execute();
if (related != null && related.length > 0) {
log.info("Spotify related artists fetched: size={} spotifyArtistId={}", related.length, id);
return toRelatedArtistResponses(related);
Expand Down Expand Up @@ -365,7 +361,7 @@ private List<TopTrackResponse> toTopTrackResponses(Track[] tracks) {
.collect(toList());
}

private List<RelatedArtistResponse> toRelatedArtistResponses(Artist[] artists) {
private List<RelatedArtistResponse> toRelatedArtistResponses(se.michaelthelin.spotify.model_objects.specification.Artist[] artists) {
if (artists == null) return List.of();
return Stream.of(artists)
.filter(Objects::nonNull)
Expand Down