Skip to content

Commit

Permalink
Merge pull request #229 from EFUB4-Jukebox/develop
Browse files Browse the repository at this point in the history
[Feat] 올해 등록된 핀 갯수 listenedDate가 아닌 createdTime을 기준으로 변경
  • Loading branch information
seohyun-lee authored Oct 1, 2024
2 parents fb889c5 + 7f31636 commit 525fe9d
Show file tree
Hide file tree
Showing 31 changed files with 238 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/github-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
echo "${{ secrets.APPLICATION_PROD }}" > ./application.yml
# 환경별 yml 파일 생성(2) - dev
- name: Bake application-dev.yml
- name: Make application-dev.yml
if: contains(github.ref, 'deploy')
run: |
cd ./src/main/resources
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ EFUB 4기 SWS 3팀 "SongPin" 프로젝트 백엔드 레포지토리입니다.
<img alt="Instagram" src="https://img.shields.io/badge/-Instagram-white?logo=Instagram&logoColor=d42121">
</a>
<br>
<a href="https://songpin.vercel.app/">
<a href="https://songpin.kr/">
<img alt="SongPin WebSite" src="https://img.shields.io/badge/SongPin%20WebSite-%23B6FF5A">
</a>
</div>
Expand Down Expand Up @@ -82,7 +82,7 @@ EFUB 4기 SWS 3팀 "SongPin" 프로젝트 백엔드 레포지토리입니다.
| :---: | :---: | :---: | :---: |
| <img src="https://avatars.githubusercontent.com/u/32611398?v=4" width="125" height="125"/> | <img src="https://avatars.githubusercontent.com/u/141399892?v=4" width="125" height="125"/> | <img src="https://avatars.githubusercontent.com/u/87927105?v=4" width="125" height="125"/> | <img src="https://avatars.githubusercontent.com/u/124586544?v=4" width="125" height="125"/> |
| [@seohyun-lee](https://github.com/seohyun-lee) | [@jud1thDev](https://github.com/jud1thDev) | [@crHwang0822](https://github.com/crHwang0822) | [@gkdudans](https://github.com/gkdudans) |
| [배포] 서버 배포 및 CI/CD 구축<br>[Place] 장소 검색, 장소 상세정보 조회 기능<br>[Map] 지도 마커 표시 목적 장소 좌표들 가져오기 기능 (기간&장르 필터링, 유저별, 플레이리스트별)<br>[Follow] 유저 검색, 유저의 팔로잉/팔로워 목록 조회, 타 유저를 팔로우, 팔로잉 취소/팔로워 삭제 기능<br>[Alarm] 알림 목록 조회, 구독 기능<br>[Statistics] 서비스의 종합 통계, 장르별 통계 조회 기능 | [Pin] 핀 생성, 조회, 수정, 삭제 기능<br> [Spotify] 핀 생성 시 Spotify API를 활용한 노래 검색 기능 <br> [Song] 노래 상세정보 조회, 해당 노래에 대한 전체 핀 목록/내 핀 목록 조회, 노래 검색 기능 <br> [Feed] 타 유저/내 핀 피드 조회, 내 핀 피드 검색, 내 핀피드 캘린더 기능 | [Member] 회원가입, 회원 탈퇴, 프로필 조회, 프로필 편집, Redis를 활용한 비밀번호 재설정 메일 전송 및 비밀번호 변경 기능<br>[Auth] 스프링 시큐리티, JWT, Redis를 활용한 토큰 (재)발급/인증/인가 구현, 로그인/로그아웃 기능 | [Playlist, PlaylistPin] 플레이리스트 생성, 핀 담기, 메인, 검색, 상세정보 조회, 편집, 삭제, 내 플레이리스트 목록/타 유저 플레이리스트 목록 조회 기능<br>[Bookmark] 북마크 생성, 취소, 내 북마크 목록 조회 기능<br>[Home] 최근 생성된 핀 & 장소 조회 기능 |
| [배포] 서버 배포 및 CI/CD 구축<br>[Place] 장소 검색, 장소 상세정보 조회 기능<br>[Map] 지도 마커 표시 목적 장소 좌표들 가져오기 기능 (기간&장르 필터링, 유저별, 플레이리스트별)<br>[Follow] 유저 검색, 유저의 팔로잉/팔로워 목록 조회, 타 유저를 팔로우, 팔로잉 취소/팔로워 삭제 기능<br>[Alarm] SSE 알림 구독, 알림 목록 조회 기능<br>[Statistics] 서비스의 종합 통계, 장르별 통계 조회 기능 | [Pin] 핀 생성, 조회, 수정, 삭제 기능<br> [Spotify] 핀 생성 시 Spotify API를 활용한 노래 검색 기능 <br> [Song] 노래 상세정보 조회, 해당 노래에 대한 전체 핀 목록/내 핀 목록 조회, 노래 검색 기능 <br> [Feed] 타 유저/내 핀 피드 조회, 내 핀 피드 검색, 내 핀피드 캘린더 기능 | [Member] 회원가입, 회원 탈퇴, 프로필 조회, 프로필 편집, Redis를 활용한 비밀번호 재설정 메일 전송 및 비밀번호 변경 기능<br>[Auth] 스프링 시큐리티, JWT, Redis를 활용한 토큰 (재)발급/인증/인가 구현, 로그인/로그아웃 기능 | [Playlist, PlaylistPin] 플레이리스트 생성, 핀 담기, 메인, 검색, 상세정보 조회, 편집, 삭제, 내 플레이리스트 목록/타 유저 플레이리스트 목록 조회 기능<br>[Bookmark] 북마크 생성, 취소, 내 북마크 목록 조회 기능<br>[Home] 최근 생성된 핀 & 장소 조회 기능 |

<br>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import sws.songpin.domain.alarm.dto.ssedata.AlarmDefaultDataDto;
import sws.songpin.domain.alarm.repository.AlarmRepository;
Expand All @@ -12,10 +11,10 @@
import sws.songpin.domain.member.service.MemberService;

import java.io.IOException;
import java.util.Optional;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class EmitterService {
private final EmitterRepository emitterRepository;
Expand All @@ -26,7 +25,12 @@ public class EmitterService {

public SseEmitter subscribe() {
Member member = memberService.getCurrentMember();
SseEmitter emitter = registerEmitter(member.getMemberId());
Long memberId = member.getMemberId();

// 이미 존재하는 Emitter가 있는지 확인
SseEmitter emitter = Optional.ofNullable(emitterRepository.get(memberId))
.orElseGet(() -> registerEmitter(memberId));

sendToClientIfNewAlarmExists(member);
return emitter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import java.util.Optional;

public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {
Optional<Bookmark> findByPlaylistAndMember(Playlist playlist, Member member);
boolean existsByPlaylistAndMember(Playlist playlist, Member member);
List<Bookmark> findAllByPlaylistAndMember(Playlist playlist, Member member);
@Query("SELECT b FROM Bookmark b WHERE b.member = :member ORDER BY b.bookmarkId DESC")
List<Bookmark> findAllByMember(Member member);
void deleteAllByMember(Member member);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,17 @@ public boolean changeBookmark(BookmarkRequestDto requestDto) {
if (!member.equals(playlist.getCreator()) && playlist.getVisibility() == Visibility.PRIVATE) {
throw new CustomException(ErrorCode.UNAUTHORIZED_REQUEST);
}
Optional<Bookmark> bookmark = getBookmarkByPlaylistAndMember(playlist, member);
if(bookmark.isPresent()){
bookmark.ifPresent(bookmarkRepository::delete);
return false;
}
else{
List<Bookmark> bookmarks = bookmarkRepository.findAllByPlaylistAndMember(playlist, member);
if (bookmarks.isEmpty()) {
Bookmark newBookmark = Bookmark.builder()
.member(member)
.playlist(playlist)
.build();
bookmarkRepository.save(newBookmark);
return true;
} else { // 북마크가 존재하면 삭제
bookmarkRepository.deleteAll(bookmarks);
return false;
}
}

Expand All @@ -65,11 +64,6 @@ public BookmarkListResponseDto getAllBookmarks() {
return BookmarkListResponseDto.from(bookmarkList);
}

@Transactional(readOnly = true)
public Optional<Bookmark> getBookmarkByPlaylistAndMember(Playlist playlist, Member member) {
return bookmarkRepository.findByPlaylistAndMember(playlist, member);
}

public void deleteAllBookmarksOfMember(Member member){
bookmarkRepository.deleteAllByMember(member);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface FollowRepository extends JpaRepository<Follow,Long> {
List<Follow> findAllByFollower(Member follower);
Long countByFollowing(Member following);
Long countByFollower(Member follower);
Optional<Follow> findByFollowerAndFollowing(Member follower, Member following);
List<Follow> findAllByFollowerAndFollowing(Member follower, Member following);
void deleteAllByFollower(Member member);
void deleteAllByFollowing(Member member);
}
34 changes: 21 additions & 13 deletions src/main/java/sws/songpin/domain/follow/service/FollowService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sws.songpin.domain.follow.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sws.songpin.domain.alarm.service.AlarmService;
Expand All @@ -20,6 +21,7 @@
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
Expand All @@ -36,14 +38,15 @@ public boolean createOrDeleteFollow(FollowRequestDto requestDto) {
throw new CustomException(ErrorCode.FOLLOW_BAD_REQUEST);
}

Optional<Follow> followOptional = followRepository.findByFollowerAndFollowing(currentMember, targetMember);
if (followOptional.isPresent()) { // 팔로우가 존재하면 삭제
followRepository.delete(followOptional.get());
return false;
} else { // 팔로우 추가
// 1개가 존재하기를 기대하지만, 멀티 스레드 이슈로 여러개가 입력될 수 있음
List<Follow> follows = followRepository.findAllByFollowerAndFollowing(currentMember, targetMember);
if (follows.isEmpty()) { // 팔로우 추가
followRepository.save(FollowRequestDto.toEntity(currentMember, targetMember));
alarmService.createFollowAlarm(currentMember, targetMember);
return true;
} else { // 팔로우가 존재하면 삭제
followRepository.deleteAll(follows);
return false;
}
}

Expand All @@ -55,33 +58,38 @@ public void deleteFollower(FollowRequestDto requestDto){
throw new CustomException(ErrorCode.FOLLOW_BAD_REQUEST);
}

Optional<Follow> followOptional = followRepository.findByFollowerAndFollowing(targetMember, currentMember);
if (followOptional.isPresent()) { // 팔로우가 존재하면 삭제
followRepository.delete(followOptional.get());
} else {
// 1개가 존재하기를 기대하지만, 멀티 스레드 이슈로 여러개가 입력될 수 있음
List<Follow> follows = followRepository.findAllByFollowerAndFollowing(currentMember, targetMember);
if (follows.isEmpty()) {
throw new CustomException(ErrorCode.FOLLOW_NOT_FOUND);
} else { // 팔로우가 존재하면 삭제
followRepository.deleteAll(follows);
}
}

@Transactional(readOnly = true)
public Boolean checkIfFollowing(Member targetMember){
Member currentMember = memberService.getCurrentMember();
return checkIfFollowExists(currentMember, targetMember);
}

@Transactional(readOnly = true)
public Boolean checkIfFollower(Member targetMember) {
Member currentMember = memberService.getCurrentMember();
return checkIfFollowExists(targetMember, currentMember);
}

@Transactional(readOnly = true)
public Boolean checkIfFollowExists(Member follower, Member following) {
if (follower.equals(following)) {
return null;
}
Optional<Follow> followOptional = followRepository.findByFollowerAndFollowing(follower, following);
if (followOptional.isPresent()) {
return true;
} else {
// 1개가 존재하기를 기대하지만, 멀티 스레드 이슈로 여러개가 입력될 수 있음
List<Follow> follows = followRepository.findAllByFollowerAndFollowing(follower, following);
if (follows.isEmpty()) {
return false;
} else {
return true;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import sws.songpin.global.exception.ErrorCode;

@Service
@Transactional
@RequiredArgsConstructor
public class GenreService {
private final GenreRepository genreRepository;
Expand All @@ -29,6 +28,7 @@ public void initGenres() {
}
}

@Transactional(readOnly = true)
public Genre getGenreByGenreName(GenreName genreName) {
return genreRepository.findByGenreName(genreName)
.orElseThrow(() -> new CustomException(ErrorCode.GENRE_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.util.stream.Collectors;

@Service
@Transactional
@RequiredArgsConstructor
public class HomeService {
private final MemberService memberService;
Expand All @@ -30,7 +29,7 @@ public HomeResponseDto getHome() {
List<Place> places = placeRepository.findTop3ByOrderByPlaceIdDesc();
// pinList
List<PinBasicUnitDto> pinList = pins.stream()
.map(pin -> PinBasicUnitDto.from(pin, pin.getCreator().equals(currentMember)))
.map(pin -> PinBasicUnitDto.from(pin, pin.getSong(), pin.getPlace(), pin.getGenre().getGenreName(), pin.getCreator().equals(currentMember)))
.collect(Collectors.toList());
// placeList
List<PlaceUnitDto> placeList = places.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package sws.songpin.domain.member.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.Authentication;
Expand All @@ -20,19 +19,16 @@

import static sws.songpin.global.common.EscapeSpecialCharactersService.escapeSpecialCharacters;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;

// 유저 검색
@Transactional(readOnly = true)
public MemberSearchResponseDto searchMembers(String keyword, Pageable pageable) {
// 키워드의 이스케이프 처리
String escapedWord = escapeSpecialCharacters(keyword);
Page<Member> memberPage = memberRepository.findAllByHandleContainingOrNicknameContaining(escapedWord, pageable);
Page<Member> memberPage = getSearchedMemberPage(escapedWord, pageable);
Long currentMemberId = getCurrentMember().getMemberId();

// Page<Member>를 Page<MemberUnitDto>로 변환
Expand All @@ -43,6 +39,11 @@ public MemberSearchResponseDto searchMembers(String keyword, Pageable pageable)
return MemberSearchResponseDto.from(memberUnitDtoPage);
}

@Transactional(readOnly = true)
public Page<Member> getSearchedMemberPage(String escapedWord, Pageable pageable) {
return memberRepository.findAllByHandleContainingOrNicknameContaining(escapedWord, pageable);
}

@Transactional(readOnly = true)
public Member getCurrentMember() throws CustomException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Expand Down Expand Up @@ -103,6 +104,7 @@ public boolean checkMemberExistsByHandle(String handle){
return memberRepository.existsByHandle(handle);
}

@Transactional
public Member saveMember(Member member){
return memberRepository.save(member);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import sws.songpin.domain.genre.entity.GenreName;
import sws.songpin.domain.pin.entity.Pin;
import sws.songpin.domain.place.entity.Place;
import sws.songpin.domain.song.dto.response.SongInfoDto;
import sws.songpin.domain.song.entity.Song;

import java.time.LocalDate;

Expand All @@ -18,15 +20,15 @@ public record PinBasicUnitDto(
Boolean isMine

) {
public static PinBasicUnitDto from(Pin pin, Boolean isMine) {
public static PinBasicUnitDto from(Pin pin, Song song, Place place, GenreName genreName, boolean isMine) {
return new PinBasicUnitDto(
pin.getPinId(),
SongInfoDto.from(pin.getSong()),
SongInfoDto.from(song),
pin.getListenedDate(),
pin.getPlace().getPlaceName(),
pin.getPlace().getLatitude( ),
pin.getPlace().getLongitude(),
pin.getGenre().getGenreName(),
place.getPlaceName(),
place.getLatitude(),
place.getLongitude(),
genreName,
isMine
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public record PinExistingInfoResponseDto(
String memo,
Visibility visibility
) {
public static PinExistingInfoResponseDto from(Pin pin) {
public static PinExistingInfoResponseDto from(Pin pin, GenreName genreName) {
return new PinExistingInfoResponseDto(
pin.getSong().getImgPath(),
pin.getSong().getTitle(),
pin.getSong().getArtist(),
pin.getListenedDate(),
pin.getPlace().getPlaceName(),
pin.getGenre().getGenreName(),
genreName,
pin.getMemo(),
pin.getVisibility()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ public record PinFeedUnitDto(
Boolean isMine
) {

public static PinFeedUnitDto from(Pin pin, Boolean isMine) {
public static PinFeedUnitDto from(Pin pin, GenreName genreName, Boolean isMine) {
return new PinFeedUnitDto(
pin.getPinId(),
SongInfoDto.from(pin.getSong()),
pin.getListenedDate(),
pin.getPlace().getPlaceName(),
pin.getPlace().getLatitude(),
pin.getPlace().getLongitude(),
pin.getGenre().getGenreName(),
genreName,
pin.getMemo(),
pin.getVisibility(),
isMine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public interface PinRepository extends JpaRepository <Pin, Long> {
@Query("SELECT p FROM Pin p WHERE p.creator = :creator AND YEAR(p.listenedDate) = :year AND MONTH(p.listenedDate) = :month")
List<Pin> findAllByCreatorAndDate(@Param("creator") Member creator, @Param("year") int year, @Param("month") int month);

@Query("SELECT COUNT(p) FROM Pin p WHERE YEAR(p.listenedDate) = :currentYear")
long countByListenedDateYear(@Param("currentYear") int currentYear);
@Query("SELECT COUNT(p) FROM Pin p WHERE YEAR(p.createdTime) = :currentYear")
long countByCreatedTimeYear(@Param("currentYear") int currentYear);

@Query("SELECT p.genre.genreName, COUNT(p) FROM Pin p GROUP BY p.genre ORDER BY COUNT(p) DESC")
List<Object[]> findMostPopularGenreName();
Expand Down
Loading

0 comments on commit 525fe9d

Please sign in to comment.