Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import com.back.domain.post.comment.dto.request.CommentUpdateRequestDto;
import com.back.domain.post.comment.dto.response.CommentResponseDto;
import com.back.domain.post.comment.service.CommentService;
import com.back.domain.post.post.dto.request.PostUpdateRequestDto;
import com.back.domain.post.post.dto.response.PostResponseDto;
import com.back.global.rsData.RsData;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,18 @@ public RsData<Void> deletePost(
postService.deletePost(postId);
return RsData.successOf(null); // code=200, message="success"
}

/**
* 게시글 추천(좋아요) 토글 API
* @param postId 추천할 게시글 ID
* @return 추천 상태 변경 성공 메시지
*/
@PostMapping("/{postId}/like")
@Operation(summary = "게시글 추천")
public RsData<Void> toggleLike(
@PathVariable Long postId
) {
postService.toggleLike(postId);
return RsData.successOf(null); // code=200, message="success"
}
}
12 changes: 11 additions & 1 deletion src/main/java/com/back/domain/post/post/entity/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
Expand Down Expand Up @@ -83,6 +82,9 @@ public class Post {
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTag> postTags = new ArrayList<>();

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostLike> postLikes = new ArrayList<>();

// 게시글 추천 수 (기본값: 0)
@Builder.Default
@Column(name = "like_count", nullable = false)
Expand Down Expand Up @@ -124,4 +126,12 @@ public void addTag(Tag tag) {
public void clearTags() {
this.postTags.clear();
}

public void increaseLikeCount() {
this.likeCount++;
}

public void decreaseLikeCount() {
this.likeCount--;
}
}
65 changes: 65 additions & 0 deletions src/main/java/com/back/domain/post/post/entity/PostLike.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.back.domain.post.post.entity;

import com.back.domain.post.post.enums.PostLikeStatus;
import com.back.domain.user.entity.User;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@Getter
// 같은 사용자(user_id)가 같은 게시글(post_id)을 중복 추천하지 못하도록 DB 레벨에서 보장.
@Table(name = "post_like", uniqueConstraints = {
@UniqueConstraint(columnNames = {"post_id", "user_id"})
})
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class PostLike {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

// 추천 생성 날짜
@CreatedDate
private LocalDateTime createdAt;

// 추천 상태 (기본값: 비추천)
@Builder.Default
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
private PostLikeStatus status = PostLikeStatus.NONE;

public void updateStatus(PostLikeStatus status) {
this.status = status;
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/back/domain/post/post/enums/PostLikeStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.back.domain.post.post.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum PostLikeStatus {
NONE("비추천", "해당 게시글에 추천을 아직 누르지 않은 상태"),
LIKE("추천", "해당 게시글에 추천을 누른 상태");

private final String title;
private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.back.domain.post.post.repository;

import com.back.domain.post.post.entity.Post;
import com.back.domain.post.post.entity.PostLike;
import com.back.domain.user.entity.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostLikeRepository extends JpaRepository<PostLike, Long> {
Optional<PostLike> findByPostAndUser(Post post, User user);
}
31 changes: 31 additions & 0 deletions src/main/java/com/back/domain/post/post/service/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
import com.back.domain.post.post.dto.request.PostUpdateRequestDto;
import com.back.domain.post.post.dto.response.PostResponseDto;
import com.back.domain.post.post.entity.Post;
import com.back.domain.post.post.entity.PostLike;
import com.back.domain.post.post.entity.Tag;
import com.back.domain.post.post.enums.PostLikeStatus;
import com.back.domain.post.post.enums.PostStatus;
import com.back.domain.post.post.repository.PostLikeRepository;
import com.back.domain.post.post.repository.PostRepository;
import com.back.domain.post.post.repository.TagRepository;
import com.back.domain.user.entity.User;
import com.back.global.rq.Rq;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -26,6 +30,7 @@ public class PostService {
private final PostRepository postRepository;
private final CategoryRepository categoryRepository;
private final TagRepository tagRepository;
private final PostLikeRepository postLikeRepository;
private final Rq rq;

// 게시글 작성 로직
Expand Down Expand Up @@ -129,6 +134,32 @@ public void deletePost(Long postId) {
// postRepository.delete(post);
}

@Transactional
public void toggleLike(Long postId) {
User user = rq.getActor(); // 현재 로그인한 사용자

Post post = postRepository.findById(postId)
.orElseThrow(() -> new NoSuchElementException("해당 게시글을 찾을 수 없습니다. ID: " + postId));

Optional<PostLike> existingLike = postLikeRepository.findByPostAndUser(post, user);

if (existingLike.isPresent()) {
// 이미 추천했으면 취소
existingLike.get().updateStatus(PostLikeStatus.NONE);
postLikeRepository.delete(existingLike.get());
post.decreaseLikeCount();
} else {
// 추천 추가
PostLike postLike = PostLike.builder()
.post(post)
.user(user)
.status(PostLikeStatus.LIKE)
.build();
postLikeRepository.save(postLike);
post.increaseLikeCount();
}
}

// 태그 추가 메서드
private void addTag(List<String> tagNames, Post post) {
for (String tagName : tagNames) {
Expand Down
29 changes: 22 additions & 7 deletions src/main/java/com/back/domain/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
package com.back.domain.user.entity;

import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;

import com.back.domain.post.post.entity.PostLike;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

@Entity
@Table(name = "users") // 예약어 충돌 방지를 위해 "users" 권장
Expand Down Expand Up @@ -51,6 +62,10 @@ public class User {
@Column(nullable = false, length = 20)
private String role = "USER";

// 양방향 매핑을 위한 필드
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostLike> postLikes = new ArrayList<>();

public boolean isAdmin() {
return "ADMIN".equalsIgnoreCase(role);
}
Expand Down