From 7f47452bc9c598b23384a3ecc793b09c207eb7f8 Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 11:07:52 +0900 Subject: [PATCH 01/11] =?UTF-8?q?refactor=20:=20DTO=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/artists/dto/request/CreateRequest.java | 12 ++++++++++++ .../domain/artists/dto/request/UpdateRequest.java | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java index 4fef288d..3dd0caf8 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java @@ -1,9 +1,21 @@ package com.back.web7_9_codecrete_be.domain.artists.dto.request; +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, + + @NotNull(message = "아티스트 타입은 필수로 입력해야합니다(SOLO or GROUP)") String artistType, + + @NotNull(message = "장르는 필수로 입력해야합니다.") + @Size(max = 30, message = "장르는 30자를 넘길 수 없습니다.") String genreName ) { } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java index 6e43712f..90314b0a 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java @@ -1,9 +1,17 @@ package com.back.web7_9_codecrete_be.domain.artists.dto.request; +import jakarta.validation.constraints.Size; + public record UpdateRequest( + @Size(max = 200, message = "아티스트 이름은 200자를 넘길 수 없습니다.") String artistName, + + @Size(max = 150, message = "아티스트 그룹 이름은 150자를 넘길 수 없습니다.") String artistGroup, + String artistType, + + @Size(max = 30, message = "장르 이름은 30자를 넘길 수 없습니다.") String genreName ) { } From 8d44971b103431af0b8bb1e24ee507889c0109d2 Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 11:25:43 +0900 Subject: [PATCH 02/11] =?UTF-8?q?refactor=20:=20ArtistType=20->=20ENUM=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../artists/controller/ArtistsController.java | 2 +- .../artists/dto/request/CreateRequest.java | 3 +- .../artists/dto/request/UpdateRequest.java | 3 +- .../dto/response/ArtistDetailResponse.java | 4 ++- .../domain/artists/entity/Artist.java | 11 +++--- .../domain/artists/entity/ArtistType.java | 6 ++++ .../artists/service/ArtistEnrichService.java | 22 ++++++++++-- .../domain/artists/service/ArtistService.java | 7 ++-- .../artists/service/SpotifyService.java | 36 +++++++++---------- 9 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistType.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index b0432528..6d193f83 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -43,7 +43,7 @@ public RsData enrich( public RsData create( @RequestBody CreateRequest reqBody ) { - artistService.createArtist(reqBody.artistName(), reqBody.artistGroup(), reqBody.artistGroup(), reqBody.genreName()); + artistService.createArtist(reqBody.artistName(), reqBody.artistGroup(), reqBody.artistType(), reqBody.genreName()); return RsData.success("아티스트 생성이 완료되었습니다.", null); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java index 3dd0caf8..e2333605 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java @@ -1,5 +1,6 @@ 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; @@ -12,7 +13,7 @@ public record CreateRequest( String artistGroup, @NotNull(message = "아티스트 타입은 필수로 입력해야합니다(SOLO or GROUP)") - String artistType, + ArtistType artistType, @NotNull(message = "장르는 필수로 입력해야합니다.") @Size(max = 30, message = "장르는 30자를 넘길 수 없습니다.") diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java index 90314b0a..231ced72 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/UpdateRequest.java @@ -1,5 +1,6 @@ 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( @@ -9,7 +10,7 @@ public record UpdateRequest( @Size(max = 150, message = "아티스트 그룹 이름은 150자를 넘길 수 없습니다.") String artistGroup, - String artistType, + ArtistType artistType, @Size(max = 30, message = "장르 이름은 30자를 넘길 수 없습니다.") String genreName diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/ArtistDetailResponse.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/ArtistDetailResponse.java index 91ed37d8..d18f8baa 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/ArtistDetailResponse.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/ArtistDetailResponse.java @@ -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, diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java index 23db1cc3..126ab267 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java @@ -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; @@ -36,7 +37,7 @@ 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 @@ -44,14 +45,14 @@ public Artist(String spotifyArtistId, String artistName, String artistGroup, Str 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" @@ -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; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistType.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistType.java new file mode 100644 index 00000000..e05c0e83 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistType.java @@ -0,0 +1,6 @@ +package com.back.web7_9_codecrete_be.domain.artists.entity; + +public enum ArtistType { + SOLO, + GROUP +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistEnrichService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistEnrichService.java index 8cdc4d04..ae4273d1 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistEnrichService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistEnrichService.java @@ -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; @@ -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); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index ecda161a..46101738 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -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; @@ -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); @@ -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; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/SpotifyService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/SpotifyService.java index b80b455c..58c02b70 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/SpotifyService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/SpotifyService.java @@ -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; @@ -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; @@ -63,7 +64,7 @@ public int seedKoreanArtists300() { int offset = 0; while (totalSaved < targetCount) { - Paging paging = api.searchArtists(q) + Paging paging = api.searchArtists(q) .limit(limit) .offset(offset) .build() @@ -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 @@ -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) { @@ -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"; @@ -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) { @@ -182,7 +183,7 @@ private Genre findOrCreateGenreByName(String genreName, String genreGroup) { public ArtistDetailResponse getArtistDetail( String spotifyArtistId, String artistGroup, - String artistType, + ArtistType artistType, long likeCount, long artistId, Long genreId @@ -190,7 +191,7 @@ public ArtistDetailResponse getArtistDetail( 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 albums = safeGetAlbums(api, spotifyArtistId); @@ -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 @@ -252,14 +253,9 @@ private Paging safeGetAlbums(SpotifyApi api, String artistId) { } } - /** - * ✅ related artists - * - 정상 호출 - * - related가 비거나 404면 fallback(장르 기반 검색)으로 대체 - */ private List safeGetRelated( SpotifyApi api, - Artist me, + se.michaelthelin.spotify.model_objects.specification.Artist me, String artistGroup, long artistId, Long genreId @@ -271,7 +267,7 @@ private List 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); @@ -365,7 +361,7 @@ private List toTopTrackResponses(Track[] tracks) { .collect(toList()); } - private List toRelatedArtistResponses(Artist[] artists) { + private List toRelatedArtistResponses(se.michaelthelin.spotify.model_objects.specification.Artist[] artists) { if (artists == null) return List.of(); return Stream.of(artists) .filter(Objects::nonNull) From 6361f19ba802939548d4cebee4fc90d88569cb9d Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 13:58:03 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20MVP=20=EA=B8=B0=EB=8A=A5=20Swagge?= =?UTF-8?q?r=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../artists/controller/ArtistsController.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index 6d193f83..35ab3796 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -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 saveArtist() { int saved = artistService.setArtist(); @@ -79,4 +79,29 @@ public RsData delete( artistService.delete(id); return RsData.success("아티스트 정보를 삭제했습니다.", null); } + + @Operation(summary = "아티스트 검색", description = "아티스트 이름을 입력받아 검색합니다.") + @PostMapping("/{id}") + public RsData 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() {} + } From ee37d98dd096ade970c19335202b6b4a27a9338e Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 14:26:23 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=ED=99=95=EC=9E=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20Swagger=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../artists/controller/ArtistsController.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index 35ab3796..02db38da 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -104,4 +104,28 @@ public void deleteArtistLikes( @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 + ) {} } + From 8e7c68d196a2ac97cd67bdeab7be6d97eba9375c Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 16:18:32 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=EC=95=84=ED=8B=B0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=80=EC=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../artists/controller/ArtistsController.java | 14 +++++++++----- .../artists/dto/request/SearchRequest.java | 9 +++++++++ .../artists/dto/response/SearchResponse.java | 17 +++++++++++++++++ .../domain/artists/entity/Artist.java | 3 +++ .../artists/repository/ArtistRepository.java | 3 ++- .../domain/artists/service/ArtistService.java | 18 +++++++++++++++++- .../global/error/code/ArtistErrorCode.java | 3 ++- 7 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/SearchResponse.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index 02db38da..ba8b3342 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -1,14 +1,17 @@ package com.back.web7_9_codecrete_be.domain.artists.controller; import com.back.web7_9_codecrete_be.domain.artists.dto.request.CreateRequest; +import com.back.web7_9_codecrete_be.domain.artists.dto.request.SearchRequest; import com.back.web7_9_codecrete_be.domain.artists.dto.request.UpdateRequest; 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.dto.response.SearchResponse; import com.back.web7_9_codecrete_be.domain.artists.service.ArtistService; import com.back.web7_9_codecrete_be.domain.artists.service.ArtistEnrichService; import com.back.web7_9_codecrete_be.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -80,12 +83,13 @@ public RsData delete( return RsData.success("아티스트 정보를 삭제했습니다.", null); } - @Operation(summary = "아티스트 검색", description = "아티스트 이름을 입력받아 검색합니다.") - @PostMapping("/{id}") - public RsData search( - @PathVariable Long id + @Operation(summary = "아티스트 검색", + description = "아티스트 이름 또는 키워드를 입력하면, 해당 키워드가 포함된 아티스트 목록 또는 이름에 해당하는 아티스트를 조회합니다.") + @PostMapping("/search") + public RsData> search( + @Valid @RequestBody SearchRequest reqBody ) { - return RsData.success("아티스트 검색에 성공했습니다.", null); + return RsData.success("아티스트 검색에 성공했습니다.", artistService.search(reqBody.artistName())); } @Operation(summary = "아티스트 찜하기", description = "id 에 해당하는 특정 아티스트를 찜합니다.") diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java new file mode 100644 index 00000000..9564f0a0 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java @@ -0,0 +1,9 @@ +package com.back.web7_9_codecrete_be.domain.artists.dto.request; + +import jakarta.validation.constraints.NotBlank; + +public record SearchRequest( + @NotBlank(message = "검색어를 입력해주세요") + String artistName +) { +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/SearchResponse.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/SearchResponse.java new file mode 100644 index 00000000..00f1bcf9 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/response/SearchResponse.java @@ -0,0 +1,17 @@ +package com.back.web7_9_codecrete_be.domain.artists.dto.response; + +import com.back.web7_9_codecrete_be.domain.artists.entity.Artist; + +public record SearchResponse( + String artistName, + String artistGroup, + int likeCount +) { + public static SearchResponse from(Artist artist) { + return new SearchResponse( + artist.getArtistName(), + artist.getArtistGroup(), + artist.getLikeCount() + ); + } +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java index 126ab267..8e480ed3 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java @@ -37,6 +37,9 @@ public class Artist { @Column(name = "name_ko", length = 200) private String nameKo; + @Column(name = "like_count", nullable = false) + private int likeCount = 0; + public Artist(String spotifyArtistId, String artistName, String artistGroup, ArtistType artistType, Genre genre) { this.spotifyArtistId = spotifyArtistId; this.artistName = artistName; diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistRepository.java index e749f665..96ad9568 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistRepository.java @@ -7,7 +7,6 @@ import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Optional; @Repository public interface ArtistRepository extends JpaRepository { @@ -21,4 +20,6 @@ public interface ArtistRepository extends JpaRepository { List findTop5ByArtistGroupAndIdNot(String artistGroup, long excludeId); List findTop5ByGenreIdAndIdNot(Long genreId, long excludeId); + + List findAllByArtistNameContainingIgnoreCaseOrNameKoContainingIgnoreCase(String artistName1, String artistName2); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index 46101738..4a1d46cf 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -3,6 +3,7 @@ import com.back.web7_9_codecrete_be.domain.artists.dto.request.UpdateRequest; 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.dto.response.SearchResponse; 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; @@ -98,7 +99,7 @@ public void updateArtist(Long id, UpdateRequest req) { } if (!changed) { - throw new BusinessException(ArtistErrorCode.INVALID_UPDATE_REQUEST); // "수정할 값이 없습니다" + throw new BusinessException(ArtistErrorCode.INVALID_UPDATE_REQUEST); } } @@ -109,4 +110,19 @@ public void delete(Long id) { artistRepository.delete(artist); } + @Transactional + public List search(String artistName) { + + List artists = + artistRepository.findAllByArtistNameContainingIgnoreCaseOrNameKoContainingIgnoreCase(artistName, artistName); + + if (artists.isEmpty()) { + throw new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND); + } + + return artists.stream() + .map(SearchResponse::from) + .toList(); + } + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java index d8078183..36a943cc 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java +++ b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java @@ -14,7 +14,8 @@ public enum ArtistErrorCode implements ErrorCode { ARTIST_NOT_FOUND(HttpStatus.NOT_FOUND, "AT-102", "아티스트를 찾을 수 없습니다."), ARTIST_ALREADY_EXISTS(HttpStatus.CONFLICT, "AT-103", "이미 존재하는 아티스트입니다."), SPOTIFY_NOT_FOUND(HttpStatus.NOT_FOUND, "AT-104", "존재하지 않는 Spotify Artist Key 입니다."), - INVALID_UPDATE_REQUEST(HttpStatus.BAD_REQUEST, "AT-105", "수정할 내용이 없습니다."); + INVALID_UPDATE_REQUEST(HttpStatus.BAD_REQUEST, "AT-105", "수정할 내용이 없습니다."), + INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "AT-106", "입력 값이 없습니다."); private final HttpStatus status; private final String code; From eb4ee635f68ff356dc0a1bf24b67ed083932fec7 Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 19:31:42 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/artists/controller/ArtistsController.java | 4 ++-- .../domain/artists/dto/request/CreateRequest.java | 5 +++-- .../domain/artists/dto/request/SearchRequest.java | 2 ++ .../domain/artists/service/ArtistService.java | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index ba8b3342..19ace25a 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -44,7 +44,7 @@ public RsData enrich( @Operation(summary = "아티스트 생성", description = "아티스트를 등록합니다.") @PostMapping() public RsData create( - @RequestBody CreateRequest reqBody + @Valid @RequestBody CreateRequest reqBody ) { artistService.createArtist(reqBody.artistName(), reqBody.artistGroup(), reqBody.artistType(), reqBody.genreName()); return RsData.success("아티스트 생성이 완료되었습니다.", null); @@ -68,7 +68,7 @@ public RsData artist( @PatchMapping("/{id}") public RsData update( @PathVariable Long id, - @RequestBody UpdateRequest reqBody + @Valid @RequestBody UpdateRequest reqBody ) { artistService.updateArtist(id, reqBody); return RsData.success("아티스트 정보 수정을 완료했습니다.", null); diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java index e2333605..d1cae3e7 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/CreateRequest.java @@ -1,11 +1,12 @@ 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.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; public record CreateRequest( - @NotNull(message = "아티스트 이름은 필수로 입력해야합니다.") + @NotBlank(message = "아티스트 이름은 필수로 입력해야합니다.") @Size(max = 200, message = "아티스트 이름은 200자를 넘길 수 없습니다.") String artistName, @@ -15,7 +16,7 @@ public record CreateRequest( @NotNull(message = "아티스트 타입은 필수로 입력해야합니다(SOLO or GROUP)") ArtistType artistType, - @NotNull(message = "장르는 필수로 입력해야합니다.") + @NotBlank(message = "장르는 필수로 입력해야합니다.") @Size(max = 30, message = "장르는 30자를 넘길 수 없습니다.") String genreName ) { diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java index 9564f0a0..79de005d 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/dto/request/SearchRequest.java @@ -1,9 +1,11 @@ package com.back.web7_9_codecrete_be.domain.artists.dto.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; public record SearchRequest( @NotBlank(message = "검색어를 입력해주세요") + @Size(max = 200, message = "아티스트 이름은 200자를 넘길 수 없습니다.") String artistName ) { } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index 4a1d46cf..3b627f92 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -110,9 +110,8 @@ public void delete(Long id) { artistRepository.delete(artist); } - @Transactional + @Transactional(readOnly = true) public List search(String artistName) { - List artists = artistRepository.findAllByArtistNameContainingIgnoreCaseOrNameKoContainingIgnoreCase(artistName, artistName); From 99479a802cde0753fdaad96fbdfad3a5e9929127 Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Tue, 16 Dec 2025 23:29:21 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat=20:=20=EC=95=84=ED=8B=B0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B0=9C=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../artists/controller/ArtistsController.java | 9 ++++++--- .../domain/artists/entity/Artist.java | 8 ++++++++ .../domain/artists/entity/ArtistLike.java | 9 ++++++--- .../repository/ArtistLikeRepository.java | 3 +++ .../domain/artists/service/ArtistService.java | 17 +++++++++++++++++ .../global/error/code/ArtistErrorCode.java | 3 ++- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index 19ace25a..5c5c1902 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -92,11 +92,14 @@ public RsData> search( return RsData.success("아티스트 검색에 성공했습니다.", artistService.search(reqBody.artistName())); } - @Operation(summary = "아티스트 찜하기", description = "id 에 해당하는 특정 아티스트를 찜합니다.") + @Operation(summary = "아티스트 찜하기", description = "id 에 해당하는 특정 아티스트를 찜합니다. 로그인 상태에서만 가능합니다.") @PostMapping("/likes/{id}") - public void artistLikes( + public RsData artistLikes( @PathVariable Long id - ) {} + ) { + artistService.likeArtist(id); + return RsData.success("아티스트 찜 성공", null); + } @Operation(summary = "아티스트 찜 해체", description = "id 에 해당하는 아티스트에게 등록했던 찜을 해제합니다.") @DeleteMapping("/likes/{id}") diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java index 8e480ed3..1cea885d 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/Artist.java @@ -77,5 +77,13 @@ public void changeGenre(Genre genre) { this.genre = genre; } + public void increaseLikeCount() { + this.likeCount++; + } + public void decreaseLikeCount() { + if (this.likeCount > 0) { + this.likeCount--; + } + } } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java index 9397d4e8..fa77e098 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java @@ -1,5 +1,6 @@ package com.back.web7_9_codecrete_be.domain.artists.entity; +import com.back.web7_9_codecrete_be.domain.users.entity.User; import jakarta.persistence.*; import lombok.Getter; @@ -17,14 +18,16 @@ public class ArtistLike { @Column(name = "created_date", nullable = false) private LocalDateTime createdDate; - // TODO : 추후 user Entity 보고 확인 예정 우선 주석 처리 - /* @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "genre_id", nullable = false) private User user; - */ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "artist_id", nullable = false) private Artist artist; + + public ArtistLike(Artist artist, User user) { + this.artist = artist; + this.user = user; + } } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java index ffc81403..9c363f68 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java @@ -1,10 +1,13 @@ package com.back.web7_9_codecrete_be.domain.artists.repository; +import com.back.web7_9_codecrete_be.domain.artists.entity.Artist; import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistLike; +import com.back.web7_9_codecrete_be.domain.users.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ArtistLikeRepository extends JpaRepository { long countByArtistId(Long artistId); + boolean existsByArtistAndUser(Artist artist, User user); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index 3b627f92..0c117b56 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -5,12 +5,15 @@ import com.back.web7_9_codecrete_be.domain.artists.dto.response.ArtistDetailResponse; import com.back.web7_9_codecrete_be.domain.artists.dto.response.SearchResponse; import com.back.web7_9_codecrete_be.domain.artists.entity.Artist; +import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistLike; 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; +import com.back.web7_9_codecrete_be.domain.users.entity.User; import com.back.web7_9_codecrete_be.global.error.code.ArtistErrorCode; import com.back.web7_9_codecrete_be.global.error.exception.BusinessException; +import com.back.web7_9_codecrete_be.global.rq.Rq; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -25,6 +28,7 @@ public class ArtistService { private final ArtistRepository artistRepository; private final GenreService genreService; private final ArtistLikeRepository artistLikeRepository; + private final Rq rq; @Transactional public int setArtist() { @@ -124,4 +128,17 @@ public List search(String artistName) { .toList(); } + @Transactional + public void likeArtist(Long artistId) { + User user = rq.getUser(); + Artist artist = artistRepository.findById(artistId) + .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); + + if(artistLikeRepository.existsByArtistAndUser(artist, user)) { + throw new BusinessException(ArtistErrorCode.LIKES_ALREADY_EXISTS); + } + artistLikeRepository.save(new ArtistLike(artist, user)); + artist.increaseLikeCount(); + } + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java index 36a943cc..1fd3e76e 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java +++ b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java @@ -15,7 +15,8 @@ public enum ArtistErrorCode implements ErrorCode { ARTIST_ALREADY_EXISTS(HttpStatus.CONFLICT, "AT-103", "이미 존재하는 아티스트입니다."), SPOTIFY_NOT_FOUND(HttpStatus.NOT_FOUND, "AT-104", "존재하지 않는 Spotify Artist Key 입니다."), INVALID_UPDATE_REQUEST(HttpStatus.BAD_REQUEST, "AT-105", "수정할 내용이 없습니다."), - INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "AT-106", "입력 값이 없습니다."); + INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "AT-106", "입력 값이 없습니다."), + LIKES_ALREADY_EXISTS(HttpStatus.CONFLICT, "AT-107", "이미 좋아요를 눌렀습니다."); private final HttpStatus status; private final String code; From 80348ba269f18da95b42cdf8dce48320de29678f Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Wed, 17 Dec 2025 00:17:05 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat=20:=20=EC=95=84=ED=8B=B0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B0=9C=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../artists/controller/ArtistsController.java | 7 +++++-- .../domain/artists/entity/ArtistLike.java | 3 +++ .../artists/repository/ArtistLikeRepository.java | 3 +++ .../domain/artists/service/ArtistService.java | 14 +++++++++++++- .../global/error/code/ArtistErrorCode.java | 3 ++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index 5c5c1902..8297f980 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -103,9 +103,12 @@ public RsData artistLikes( @Operation(summary = "아티스트 찜 해체", description = "id 에 해당하는 아티스트에게 등록했던 찜을 해제합니다.") @DeleteMapping("/likes/{id}") - public void deleteArtistLikes( + public RsData deleteArtistLikes( @PathVariable Long id - ) {} + ) { + artistService.deleteLikeArtist(id); + return RsData.success("아티스트 찜 해제 성공", null); + } @Operation(summary = "개인화된 공연 리스트 생성", description = "유저가 찜한 아티스트를 기반으로 공연 리스트를 생성합니다.") @PostMapping("/list") diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java index fa77e098..066f94fe 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java @@ -3,11 +3,13 @@ import com.back.web7_9_codecrete_be.domain.users.entity.User; import jakarta.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; @Entity @Getter +@NoArgsConstructor @Table(name = "artist_like") public class ArtistLike { @Id @@ -27,6 +29,7 @@ public class ArtistLike { private Artist artist; public ArtistLike(Artist artist, User user) { + this.createdDate = LocalDateTime.now(); this.artist = artist; this.user = user; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java index 9c363f68..002b2a3d 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/repository/ArtistLikeRepository.java @@ -6,8 +6,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface ArtistLikeRepository extends JpaRepository { long countByArtistId(Long artistId); boolean existsByArtistAndUser(Artist artist, User user); + Optional findByArtistAndUser(Artist artist, User user); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index 0c117b56..efc478a6 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -14,6 +14,7 @@ import com.back.web7_9_codecrete_be.global.error.code.ArtistErrorCode; import com.back.web7_9_codecrete_be.global.error.exception.BusinessException; import com.back.web7_9_codecrete_be.global.rq.Rq; +import lombok.AccessLevel; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,7 +22,7 @@ import java.util.List; @Service -@RequiredArgsConstructor +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class ArtistService { private final SpotifyService spotifyService; @@ -141,4 +142,15 @@ public void likeArtist(Long artistId) { artist.increaseLikeCount(); } + @Transactional + public void deleteLikeArtist(Long artistId) { + User user = rq.getUser(); + Artist artist = artistRepository.findById(artistId) + .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); + ArtistLike likes = artistLikeRepository.findByArtistAndUser(artist, user) + .orElseThrow(() -> new BusinessException(ArtistErrorCode.LIKES_NOT_FOUND)); + artistLikeRepository.delete(likes); + artist.decreaseLikeCount(); + } + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java index 1fd3e76e..f75260b5 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java +++ b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ArtistErrorCode.java @@ -16,7 +16,8 @@ public enum ArtistErrorCode implements ErrorCode { SPOTIFY_NOT_FOUND(HttpStatus.NOT_FOUND, "AT-104", "존재하지 않는 Spotify Artist Key 입니다."), INVALID_UPDATE_REQUEST(HttpStatus.BAD_REQUEST, "AT-105", "수정할 내용이 없습니다."), INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "AT-106", "입력 값이 없습니다."), - LIKES_ALREADY_EXISTS(HttpStatus.CONFLICT, "AT-107", "이미 좋아요를 눌렀습니다."); + LIKES_ALREADY_EXISTS(HttpStatus.CONFLICT, "AT-107", "이미 찜한 아티스트입니다."), + LIKES_NOT_FOUND(HttpStatus.NOT_FOUND, "AT-108", "찜한 아티스트가 아닙니다."); private final HttpStatus status; private final String code; From 0b90afce1826006909a073a166573d7a0960155c Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Wed, 17 Dec 2025 00:25:31 +0900 Subject: [PATCH 09/11] =?UTF-8?q?fix=20:=20ArtistLike=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=BB=AC=EB=9F=BC=EB=AA=85=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(genre=5Fid=20->=20user=5Fid)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web7_9_codecrete_be/domain/artists/entity/ArtistLike.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java index 066f94fe..c14dab85 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java @@ -21,7 +21,7 @@ public class ArtistLike { private LocalDateTime createdDate; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "genre_id", nullable = false) + @JoinColumn(name = "user_id", nullable = false) private User user; @ManyToOne(fetch = FetchType.LAZY) From a7c23850c6a1283aee964636ed93285cb14a739a Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Wed, 17 Dec 2025 09:32:03 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20Rq=20=EC=84=A0=EC=96=B8=20ser?= =?UTF-8?q?vice=20->=20controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/artists/controller/ArtistsController.java | 9 +++++++-- .../domain/artists/service/ArtistService.java | 6 ++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java index 8297f980..6b03b4b1 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java @@ -8,6 +8,8 @@ import com.back.web7_9_codecrete_be.domain.artists.dto.response.SearchResponse; import com.back.web7_9_codecrete_be.domain.artists.service.ArtistService; import com.back.web7_9_codecrete_be.domain.artists.service.ArtistEnrichService; +import com.back.web7_9_codecrete_be.domain.users.entity.User; +import com.back.web7_9_codecrete_be.global.rq.Rq; import com.back.web7_9_codecrete_be.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -24,6 +26,7 @@ public class ArtistsController { private final ArtistService artistService; private final ArtistEnrichService enrichService; + private final Rq rq; @Operation(summary = "아티스트 저장", description = "임의의 가수(or 팀)을 DB에 저장합니다.") @GetMapping("/saved") @@ -97,7 +100,8 @@ public RsData> search( public RsData artistLikes( @PathVariable Long id ) { - artistService.likeArtist(id); + User user = rq.getUser(); + artistService.likeArtist(id, user); return RsData.success("아티스트 찜 성공", null); } @@ -106,7 +110,8 @@ public RsData artistLikes( public RsData deleteArtistLikes( @PathVariable Long id ) { - artistService.deleteLikeArtist(id); + User user = rq.getUser(); + artistService.deleteLikeArtist(id, user); return RsData.success("아티스트 찜 해제 성공", null); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index efc478a6..a5f5536b 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -130,8 +130,7 @@ public List search(String artistName) { } @Transactional - public void likeArtist(Long artistId) { - User user = rq.getUser(); + public void likeArtist(Long artistId, User user) { Artist artist = artistRepository.findById(artistId) .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); @@ -143,8 +142,7 @@ public void likeArtist(Long artistId) { } @Transactional - public void deleteLikeArtist(Long artistId) { - User user = rq.getUser(); + public void deleteLikeArtist(Long artistId, User user) { Artist artist = artistRepository.findById(artistId) .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); ArtistLike likes = artistLikeRepository.findByArtistAndUser(artist, user) From baa7a3f3c811ba0d910c128191be2b43cb0f7522 Mon Sep 17 00:00:00 2001 From: Jung Yunseo Date: Wed, 17 Dec 2025 09:44:45 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/artists/entity/ArtistLike.java | 6 +++++- .../domain/artists/service/ArtistService.java | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java index c14dab85..a67296cb 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/entity/ArtistLike.java @@ -4,12 +4,16 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; + @Entity @Getter @NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) @Table(name = "artist_like") public class ArtistLike { @Id @@ -17,6 +21,7 @@ public class ArtistLike { @Column(name = "artist_like_id") private long id; + @CreatedDate @Column(name = "created_date", nullable = false) private LocalDateTime createdDate; @@ -29,7 +34,6 @@ public class ArtistLike { private Artist artist; public ArtistLike(Artist artist, User user) { - this.createdDate = LocalDateTime.now(); this.artist = artist; this.user = user; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java index a5f5536b..f34bcf03 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java @@ -29,7 +29,12 @@ public class ArtistService { private final ArtistRepository artistRepository; private final GenreService genreService; private final ArtistLikeRepository artistLikeRepository; - private final Rq rq; + + @Transactional(readOnly = true) + public Artist findArtist(Long artistId) { + return artistRepository.findById(artistId) + .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); + } @Transactional public int setArtist() { @@ -131,9 +136,7 @@ public List search(String artistName) { @Transactional public void likeArtist(Long artistId, User user) { - Artist artist = artistRepository.findById(artistId) - .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); - + Artist artist = findArtist(artistId); if(artistLikeRepository.existsByArtistAndUser(artist, user)) { throw new BusinessException(ArtistErrorCode.LIKES_ALREADY_EXISTS); } @@ -143,8 +146,7 @@ public void likeArtist(Long artistId, User user) { @Transactional public void deleteLikeArtist(Long artistId, User user) { - Artist artist = artistRepository.findById(artistId) - .orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND)); + Artist artist = findArtist(artistId); ArtistLike likes = artistLikeRepository.findByArtistAndUser(artist, user) .orElseThrow(() -> new BusinessException(ArtistErrorCode.LIKES_NOT_FOUND)); artistLikeRepository.delete(likes);