[클론 코딩] 네이버 카페 - 게시글 '좋아요' 등록, 취소

2024. 6. 6. 23:28Project/Naver Cafe

  네이버 카페에는 마음에 드는 게시글에 '좋아요'를 남길 수가 있는데, 이번에는 이 '좋아요' 기능을 구현하고자 한다. 해당 기능은 '좋아요'를 등록, 취소할 수 있으며, 추가된 좋아요 정보들은 게시글이 삭제될 때, 같이 삭제 된다.


Favorite

package CloneCoding.NaverCafe.domain.favorite;

@Entity
@Table(name = "FAVORITE")
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Favorite {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column(name = "PROFILE_IMAGE")
    private String profileImage;

    @Column(name = "ACCOUNT_ID")
    private String accountId;

    @Column(name = "NICKNAME")
    private String nickname;

    @Column(name = "ACTIVE_AT")
    private LocalDateTime activeAt;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "NORMAL_ID")
    private Normal normalId;

    public static Favorite create(CafeMember cafeMember, Normal normal) {
        return Favorite.builder()
                .profileImage(cafeMember.getProfileImage())
                .accountId(cafeMember.getAccountId())
                .nickname(cafeMember.getNickname())
                .activeAt(LocalDateTime.now())
                .normalId(normal)
                .build();
    }

}

 

  • create() : Favorite 객체를 생성하고, 생성된 객체를 반환

FavoriteController

package CloneCoding.NaverCafe.domain.favorite.controller;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/cafe/{cafe_url}/{normal_id}")
public class FavoriteController {

    private final FavoriteService favoriteService;

    @PostMapping("/favorite")
    public String addFavorite(@PathVariable("cafe_url") String url,
                              @PathVariable("normal_id") Long id,
                              @RequestHeader("Authorization") String token) {
        log.info("게시글 좋아요 추가 요청");
        return favoriteService.addFavorite(url, id, token);
    }

    @DeleteMapping("/favorite")
    public String subFavorite(@PathVariable("cafe_url") String url,
                              @PathVariable("normal_id") Long id,
                              @RequestHeader("Authorization") String token) {
        log.info("게시글 좋아요 취소 요청");
        return favoriteService.subFavorite(url, id, token);
    }

}

 

  • addFavorite() : 카페 url, 게시글 id, 토큰 정보로 FavoriteService의 addFavorite()를 호출 후 결과 메시지 반환
  • subFavorite() : 카페 url, 게시글 id, 토큰 정보로 FavoriteService의 subFavorite()를 호출 후 결과 메시지 반환

FavoriteServiceImpl

package CloneCoding.NaverCafe.domain.favorite.service;

import static CloneCoding.NaverCafe.message.SystemMessage.ADD_FAVORITE_COMPLETE;
import static CloneCoding.NaverCafe.message.SystemMessage.SUB_FAVORITE_COMPLETE;

@Service
@RequiredArgsConstructor
public class FavoriteServiceImpl implements FavoriteService {

    private final FavoriteRepository favoriteRepository;
    private final CafeRepository cafeRepository;
    private final NormalRepository normalRepository;
    private final CafeMemberRepository cafeMemberRepository;
    private final AesUtil aesUtil;

    @Transactional
    @Override
    public String addFavorite(String url, Long id, String token) {

        CafeMember user = checkCafeMember(url, token);
        Normal article = normalRepository.findByIdWithLock(id)
                .orElseThrow(() -> new NoSuchElementException("게시글 정보를 찾을 수 없습니다."));

        Favorite favorite = Favorite.create(user, article);
        article.addFavoriteCount();

        favoriteRepository.save(favorite);
        normalRepository.save(article);

        return ADD_FAVORITE_COMPLETE.getMessage();

    }

    @Transactional
    @Override
    public String subFavorite(String url, Long id, String token) {

        CafeMember user = checkCafeMember(url, token);
        Normal article = normalRepository.findByIdWithLock(id)
                .orElseThrow(() -> new NoSuchElementException("게시글 정보를 찾을 수 없습니다."));

        Favorite findFavorite = favoriteRepository.findByAccountId(user.getAccountId());
        article.subFavoriteCount();

        favoriteRepository.delete(findFavorite);
        normalRepository.save(article);

        return SUB_FAVORITE_COMPLETE.getMessage();

    }

    private CafeMember checkCafeMember(String url, String token) {
        String accountId = aesUtil.aesDecode(token);
        Cafe findCafe = cafeRepository.findByUrl(url);
        return cafeMemberRepository.findByAccountId(findCafe, accountId);
    }

}

 

  • addFavorite() : 생성한 Favorite 객체와 수정한 Normal 객체(엔티티)를 저장(수정) 후, 결과 메시지 반환
  • subFavorite() : 조회한 Favorite 객체(엔티티) 삭제, 수정한 Normal 객체(엔티티) 저장 후, 결과 메시지 반환

QueryFavoriteRepositoryImpl

package CloneCoding.NaverCafe.domain.favorite.repository;

import static CloneCoding.NaverCafe.domain.favorite.QFavorite.favorite;

@Repository
@RequiredArgsConstructor
public class QueryFavoriteRepositoryImpl implements QueryFavoriteRepository {

    private final JPAQueryFactory query;

    @Override
    public Favorite findByAccountId(String accountId) {
        return Optional.ofNullable(query
                        .selectFrom(favorite)
                        .where(favorite.accountId.eq(accountId))
                        .fetchOne())
                .orElseThrow(() -> new NoSuchElementException("좋아요 정보를 찾을 수 없습니다."));
    }

}

 

  • findByAccountId() : accountId 정보로 Favorite 객체(엔티티)를 조회 후, 조회한 객체 반환

API TEST

API TEST - 게시글 좋아요 추가

 

DB - 좋아요 추가

 

DB - 게시글 좋아요 개수

 

  게시글에 '좋아요' 추가(등록) 요청에 대한 테스트 결과이다. 우선 API 요청에 대한 결과 메시지는 정상적으로 출력되었다. DB의 FAVORITE 테이블에 엔티티(레코드)가 추가 된걸 확인할 수 있으며, NORMAL 테이블에서 해당 게시글의 좋아요 개수가 '0'에서 '1'로 늘어난걸 확인할 수 있다.

 

API TEST - 게시글 좋아요 취소

 

DB - 좋아요 삭제

 

DB - 좋아요 취소 후, 게시글 좋아요 개수

 

  게시글에 '좋아요' 취소 요청에 대한 테스트 결과이다. 우선 API 요청에 대한 결과 메시지는 정상적으로 출력되었다. DB의 FAVORITE 테이블에 엔티티(레코드)가 삭제 된걸 확인할 수 있으며, NORMAL 테이블에서 해당 게시글의 좋아요 개수가 '1'에서 '0'으로 줄어든걸 확인할 수 있다.