Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
89fd689
fix : 검색 κ²°κ³Ό μˆœμ„œλŒ€λ‘œ λ°˜ν™˜ν•˜λ„λ‘ μˆ˜μ •
elive7 Dec 1, 2025
e8f9e83
[SCRUM-261] feat: Note에 ENUM νƒ€μž… 컬럼 'type' μΆ”κ°€
Jan 4, 2026
9d261eb
[SCRUM-261] feat: NoLyricsForNoteException μΆ”κ°€
Jan 4, 2026
924bde4
[SCRUM-261] feat: NoteCreate.validateLyricsForType μΆ”κ°€ν•˜μ—¬ 가사 해석 λ…ΈνŠΈμž„μ—λ„ 가…
Jan 4, 2026
c2fe276
[SCRUM-261] fix: μ‚¬μš©μžμ˜ λ…ΈνŠΈ 검색 API의 URIλ₯Ό /api/v1/notes -> /api/v1/users/…
Jan 4, 2026
5946933
Revert "setting: workflow μˆ˜μ •"
elive7 Jan 5, 2026
7627080
Revert "setting: workflow μˆ˜μ •"
elive7 Jan 5, 2026
e9a46cf
Revert "setting: gitμ„œλ²„κ°€ μ•„λ‹ˆλΌ prod, devμ—μ„œ workflow λŒμ•„κ°€λ„λ‘ μˆ˜μ •"
elive7 Jan 5, 2026
15d75dd
fix: workflow_dispatch λ“€μ—¬μ“°κΈ° μˆ˜μ •
elive7 Jan 6, 2026
f92e400
setting: gitμ„œλ²„κ°€ μ•„λ‹ˆλΌ prod, devμ—μ„œ workflow λŒμ•„κ°€λ„λ‘ μˆ˜μ •
elive7 Jan 6, 2026
1b4e442
setting: workflow μˆ˜μ •
elive7 Jan 6, 2026
adb9920
setting: workflow μˆ˜μ •
elive7 Jan 6, 2026
80c29b5
setting: workflow μˆ˜μ •
elive7 Jan 6, 2026
7b429e7
Revert "setting: workflow μˆ˜μ •"
elive7 Jan 5, 2026
7de4faa
[SCRUM-261] fix: getNotesOfFavoriteArtists -> getNotes μˆ˜μ •
Jan 9, 2026
cc22bcc
Merge pull request #77 from project-lyrics/SCRUM-261
jinkonu Jan 19, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/Dev-CI-CD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
[ "dev" ]
workflow_dispatch:
workflow_dispatch:

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/Prod-CI-CD.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
[ "main" ]
workflow_dispatch:
workflow_dispatch:

permissions:
contents: read
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public enum ErrorCode {
INVALID_NOTE_DELETION(HttpStatus.BAD_REQUEST, "05003", "ν•΄λ‹Ή λ…ΈνŠΈλ₯Ό μ‚­μ œν•  수 μ—†μŠ΅λ‹ˆλ‹€."),
INVALID_NOTE_UPDATE(HttpStatus.BAD_REQUEST, "05004", "ν•΄λ‹Ή λ…ΈνŠΈλ₯Ό μˆ˜μ •ν•  수 μ—†μŠ΅λ‹ˆλ‹€."),
TOO_MANY_DRAFT_NOTE(HttpStatus.BAD_REQUEST, "05005", "μž„μ‹œμ €μž₯ λ…ΈνŠΈμ˜ κ°œμˆ˜κ°€ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
NO_LYRICS_FOR_NOTE(HttpStatus.BAD_REQUEST, "05006", "ν•΄λ‹Ή λ…ΈνŠΈμ— λŒ€ν•œ 가사가 ν•„μš”ν•©λ‹ˆλ‹€."),

// Song
SONG_NOT_FOUND(HttpStatus.NOT_FOUND, "06000", "ν•΄λ‹Ή λ…Έλž˜λ₯Ό μ‘°νšŒν•  수 μ—†μŠ΅λ‹ˆλ‹€."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/notes")
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class NoteController {

private final NoteCommandService noteCommandService;
private final NoteQueryService noteQueryService;
private final ViewCommandService viewCommandService;

@PostMapping
@PostMapping("/notes")
public ResponseEntity<NoteCreateResponse> create(
@Authenticated AuthContext authContext,
@RequestBody @Valid NoteCreateRequest request
Expand All @@ -49,7 +49,7 @@ public ResponseEntity<NoteCreateResponse> create(
.body(new NoteCreateResponse(true));
}

@PatchMapping("/{noteId}")
@PatchMapping("/notes/{noteId}")
public ResponseEntity<NoteUpdateResponse> update(
@Authenticated AuthContext authContext,
@PathVariable(name = "noteId") Long noteId,
Expand All @@ -62,7 +62,7 @@ public ResponseEntity<NoteUpdateResponse> update(
.body(new NoteUpdateResponse(true));
}

@DeleteMapping("/{noteId}")
@DeleteMapping("/notes/{noteId}")
public ResponseEntity<NoteDeleteResponse> delete(
@Authenticated AuthContext authContext,
@PathVariable(name = "noteId") Long noteId
Expand All @@ -74,7 +74,7 @@ public ResponseEntity<NoteDeleteResponse> delete(
.body(new NoteDeleteResponse(true));
}

@GetMapping("/{noteId}")
@GetMapping("/notes/{noteId}")
public ResponseEntity<NoteDetailResponse> getNote(
@Authenticated AuthContext authContext,
@RequestHeader("Device-Id") String deviceId,
Expand All @@ -91,7 +91,7 @@ public ResponseEntity<NoteDetailResponse> getNote(
.body(noteQueryService.getNoteById(noteId, authContext.getId()));
}

@GetMapping
@GetMapping("/users/notes")
public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfUser(
@Authenticated AuthContext authContext,
@RequestParam(name = "hasLyrics") boolean hasLyrics,
Expand All @@ -106,21 +106,22 @@ public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfUs
.body(response);
}

@GetMapping("/favorite-artists")
public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfFavoriteArtists(
@GetMapping("/notes")
public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotes(
@Authenticated AuthContext authContext,
@RequestParam(name = "hasLyrics") boolean hasLyrics,
@RequestParam(name = "isFavoriteArtistsOnly", defaultValue = "false") boolean isFavoriteArtistsOnly,
@RequestParam(name = "cursor", required = false) Long cursor,
@RequestParam(name = "size", defaultValue = "10") int size
) {
CursorBasePaginatedResponse<NoteGetResponse> response = noteQueryService.getNotesOfFavoriteArtists(hasLyrics, authContext.getId(), cursor, size);
CursorBasePaginatedResponse<NoteGetResponse> response = noteQueryService.getNotes(hasLyrics, isFavoriteArtistsOnly, authContext.getId(), cursor, size);

return ResponseEntity
.status(HttpStatus.OK)
.body(response);
}

@GetMapping("/artists")
@GetMapping("/notes/artists")
public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfArtist(
@Authenticated AuthContext authContext,
@RequestParam(name = "hasLyrics") boolean hasLyrics,
Expand All @@ -135,7 +136,7 @@ public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfAr
.body(response);
}

@GetMapping("/songs")
@GetMapping("/notes/songs")
public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfSong(
@Authenticated AuthContext authContext,
@RequestParam(name = "hasLyrics") boolean hasLyrics,
Expand All @@ -150,7 +151,7 @@ public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesOfSo
.body(response);
}

@GetMapping("/bookmarked")
@GetMapping("/notes/bookmarked")
public ResponseEntity<CursorBasePaginatedResponse<NoteGetResponse>> getNotesBookmarked(
@Authenticated AuthContext authContext,
@RequestParam(name = "hasLyrics") boolean hasLyrics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.projectlyrics.server.domain.note.entity.NoteBackground;
import com.projectlyrics.server.domain.note.entity.NoteStatus;
import com.projectlyrics.server.domain.note.entity.NoteType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

Expand All @@ -12,6 +13,8 @@ public record NoteCreateRequest(
NoteBackground background,
@NotNull
NoteStatus status,
@NotNull(message = "λ…ΈνŠΈ μœ ν˜•μ΄ μž…λ ₯λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
NoteType noteType,
Long songId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public record NoteDetailResponse(
Long id,
String content,
String status,
String noteType,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
LocalDateTime createdAt,
LyricsGetResponse lyrics,
Expand All @@ -31,6 +32,7 @@ public static NoteDetailResponse of(Note note, List<Comment> comments, Long user
note.getId(),
note.getContent(),
note.getNoteStatus().name(),
note.getNoteType().name(),
note.getCreatedAt(),
LyricsGetResponse.from(note.getLyrics()),
UserGetResponse.from(note.getPublisher()),
Expand All @@ -50,6 +52,7 @@ public static NoteDetailResponse of(Note note, List<Comment> comments, Long user
note.getId(),
note.getContent(),
note.getNoteStatus().name(),
note.getNoteType().name(),
createdAt,
LyricsGetResponse.from(note.getLyrics()),
UserGetResponse.from(note.getPublisher()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public record NoteGetResponse(
Long id,
String content,
String status,
String noteType,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
LocalDateTime createdAt,
LyricsGetResponse lyrics,
Expand All @@ -28,6 +29,7 @@ public static NoteGetResponse of(Note note, Long userId) {
note.getId(),
note.getContent(),
note.getNoteStatus().name(),
note.getNoteType().name(),
note.getCreatedAt(),
LyricsGetResponse.from(note.getLyrics()),
UserGetResponse.from(note.getPublisher()),
Expand All @@ -44,6 +46,7 @@ public static NoteGetResponse of(Note note, Long userId, LocalDateTime createdAt
note.getId(),
note.getContent(),
note.getNoteStatus().name(),
note.getNoteType().name(),
createdAt,
LyricsGetResponse.from(note.getLyrics()),
UserGetResponse.from(note.getPublisher()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public class Note extends BaseEntity {
@Enumerated(EnumType.STRING)
private NoteStatus noteStatus;

@Enumerated(EnumType.STRING)
@Column(nullable = false, columnDefinition = "VARCHAR(50) DEFAULT 'FREE'")
private NoteType noteType;

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name="lyrics_id")
private Lyrics lyrics;
Expand All @@ -58,12 +62,14 @@ private Note(
String content,
Lyrics lyrics,
NoteStatus noteStatus,
NoteType noteType,
User publisher,
Song song
) {
this.id = id;
this.content = content;
this.noteStatus = noteStatus;
this.noteType = noteType;
this.publisher = publisher;
this.song = song;
addLyrics(lyrics);
Expand All @@ -73,17 +79,19 @@ private Note(
String content,
Lyrics lyrics,
NoteStatus noteStatus,
NoteType noteType,
User publisher,
Song song
) {
this(null, content, lyrics, noteStatus, publisher, song);
this(null, content, lyrics, noteStatus, noteType, publisher, song);
}

public static Note create(NoteCreate noteCreate) {
return new Note(
noteCreate.content(),
Lyrics.of(noteCreate.lyrics(), noteCreate.background()),
noteCreate.status(),
noteCreate.noteType(),
noteCreate.publisher(),
noteCreate.song()
);
Expand All @@ -95,6 +103,7 @@ public static Note createWithId(Long id, NoteCreate noteCreate) {
noteCreate.content(),
Lyrics.of(noteCreate.lyrics(), noteCreate.background()),
noteCreate.status(),
noteCreate.noteType(),
noteCreate.publisher(),
noteCreate.song()
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.projectlyrics.server.domain.note.entity;

import com.projectlyrics.server.domain.note.dto.request.NoteCreateRequest;
import com.projectlyrics.server.domain.note.exception.NoLyricsForNoteException;
import com.projectlyrics.server.domain.song.entity.Song;
import com.projectlyrics.server.domain.user.entity.User;

Expand All @@ -11,22 +12,32 @@ public record NoteCreate(
String lyrics,
NoteBackground background,
NoteStatus status,
NoteType noteType,
User publisher,
Song song
) {

public static NoteCreate from(NoteCreateRequest request, User publisher, Song song) {
checkNull(request.status());
checkNull(request.noteType());
checkNull(publisher);
checkNull(song);
validateLyricsForType(request.noteType(), request.lyrics());

return new NoteCreate(
request.content(),
request.lyrics(),
request.background(),
request.status(),
request.noteType(),
publisher,
song
);
}

private static void validateLyricsForType(NoteType noteType, String lyrics) {
if (noteType == NoteType.LYRICS_ANALYSIS && (lyrics == null || lyrics.isBlank())) {
throw new NoLyricsForNoteException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.projectlyrics.server.domain.note.entity;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;

@RequiredArgsConstructor
public enum NoteType {

FREE("FREE"),
QUESTION("QUESTION"),
LYRICS_ANALYSIS("LYRICS_ANALYSIS"),
;

private final String type;

@JsonValue
public String getType() {
return type;
}

@JsonCreator
public static NoteType of(String type) {
return Arrays.stream(NoteType.values())
.filter(noteType -> noteType.type.equals(type))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid NoteType: " + type));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.projectlyrics.server.domain.note.exception;

import com.projectlyrics.server.domain.common.message.ErrorCode;
import com.projectlyrics.server.global.exception.FeelinException;

public class NoLyricsForNoteException extends FeelinException {

public NoLyricsForNoteException() {
super(ErrorCode.NO_LYRICS_FOR_NOTE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public interface NoteQueryRepository {

Slice<Note> findAllByUserId(boolean hasLyrics, Long artistId, Long userId, Long cursorId, Pageable pageable);
Slice<Note> findAllByArtistIds(boolean hasLyrics, List<Long> artistsIds, Long userId, Long cursorId, Pageable pageable);
Slice<Note> findAll(boolean hasLyrics, List<Long> artistsIds, Long userId, Long cursorId, Pageable pageable);
Slice<Note> findAllByArtistId(boolean hasLyrics, Long artistId, Long userId, Long cursorId, Pageable pageable);
Slice<Note> findAllBookmarkedAndByArtistId(boolean hasLyrics, Long artistId, Long userId, Long cursorId, Pageable pageable);
Slice<Note> findAllBySongId(boolean hasLyrics, Long songId, Long userId, Long cursorId, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,33 @@ public Slice<Note> findAllByArtistIds(boolean hasLyrics, List<Long> artistsIds,
return new SliceImpl<>(content, pageable, QueryDslUtils.checkIfHasNext(pageable, content));
}

@Override
public Slice<Note> findAll(boolean hasLyrics, List<Long> artistsIds, Long userId, Long cursorId, Pageable pageable) {
List<Note> content = jpaQueryFactory
.selectFrom(note)
.leftJoin(note.lyrics).fetchJoin()
.join(note.publisher).fetchJoin()
.join(note.song).fetchJoin()
.join(song.artist).fetchJoin()
.leftJoin(note.comments).fetchJoin()
.where(
hasLyrics(hasLyrics),
artistsIds == null || artistsIds.isEmpty() ? null : note.song.artist.id.in(artistsIds),
note.deletedAt.isNull(),
QueryDslUtils.ltCursorId(cursorId, note.id),
note.publisher.notIn(
JPAExpressions.select(block.blocked)
.from(block)
.where(block.blocker.id.eq(userId).and(block.deletedAt.isNull()))
)
)
.orderBy(note.id.desc())
.limit(pageable.getPageSize() + 1)
.fetch();

return new SliceImpl<>(content, pageable, QueryDslUtils.checkIfHasNext(pageable, content));
}

@Override
public Slice<Note> findAllByArtistId(boolean hasLyrics, Long artistId, Long userId, Long cursorId, Pageable pageable) {
List<Note> content = jpaQueryFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ public CursorBasePaginatedResponse<NoteGetResponse> getNotesByUserId(boolean has
return CursorBasePaginatedResponse.of(notes);
}

public CursorBasePaginatedResponse<NoteGetResponse> getNotesOfFavoriteArtists(boolean hasLyrics, Long userId, Long cursor, int size) {
List<Long> artistsIds = favoriteArtistQueryRepository.findAllByUserIdFetchArtist(userId)
.stream()
.map(favoriteArtist -> favoriteArtist.getArtist().getId())
.toList();

Slice<NoteGetResponse> notes = noteQueryRepository.findAllByArtistIds(hasLyrics, artistsIds, userId, cursor, PageRequest.ofSize(size))
public CursorBasePaginatedResponse<NoteGetResponse> getNotes(boolean hasLyrics, boolean isFavoriteArtistsOnly, Long userId, Long cursor, int size) {
List<Long> artistsIds = null;
if (isFavoriteArtistsOnly) {
artistsIds = favoriteArtistQueryRepository.findAllByUserIdFetchArtist(userId)
.stream()
.map(favoriteArtist -> favoriteArtist.getArtist().getId())
.toList();
}

Slice<NoteGetResponse> notes = noteQueryRepository.findAll(hasLyrics, artistsIds, userId, cursor, PageRequest.ofSize(size))
.map(note -> NoteGetResponse.of(note, userId));

return CursorBasePaginatedResponse.of(notes);
Expand Down
Loading
Loading