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 @@ -8,12 +8,14 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sku.refit.domain.auth.dto.request.LoginRequest;
import com.sku.refit.domain.auth.dto.response.TokenResponse;
import com.sku.refit.domain.auth.exception.AuthErrorCode;
import com.sku.refit.domain.user.entity.Role;
import com.sku.refit.domain.user.entity.User;
import com.sku.refit.domain.user.exception.UserErrorCode;
import com.sku.refit.domain.user.repository.UserRepository;
Expand All @@ -37,6 +39,7 @@ public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final JwtProvider jwtProvider;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

@Override
@Transactional
Expand Down Expand Up @@ -97,6 +100,23 @@ public String reissueAccessToken(String refreshToken) {
@Transactional
public TokenResponse testLogin() {

User user = userRepository.findByUsername(testUsername).orElse(null);

if (user == null) {
user =
User.builder()
.profileImageUrl(testUsername + ".png")
.nickname("김다입")
.username(testUsername)
.password(passwordEncoder.encode(testPassword))
.locationConsent(true)
.role(Role.ROLE_USER)
.build();

userRepository.save(user);
log.info("테스트 사용자 생성됨: {}", user.getUsername());
}

UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(testUsername, testPassword);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.controller;

import java.util.List;

import jakarta.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.sku.refit.domain.comment.dto.request.CommentRequest;
import com.sku.refit.domain.comment.dto.response.CommentDetailResponse;
import com.sku.refit.global.response.BaseResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "커뮤니티 댓글", description = "커뮤니티 댓글 관련 API")
@RequestMapping("/api/comments")
public interface CommentController {

@PostMapping("/new")
@Operation(summary = "새 댓글 작성", description = "특정 게시글의 댓글을 작성합니다.")
ResponseEntity<BaseResponse<CommentDetailResponse>> createComment(
@RequestBody @Valid CommentRequest request,
@Parameter(description = "댓글을 작성할 게시글 식별자", example = "1") @RequestParam Long postId);

@GetMapping
@Operation(summary = "특정 게시글의 댓글 조회", description = "특정 게시글의 댓글 리스트를 조회합니다.")
ResponseEntity<BaseResponse<List<CommentDetailResponse>>> getAllCommentsByPostId(
@Parameter(description = "댓글을 작성할 게시글 식별자", example = "1") @RequestParam Long postId);

@PutMapping("{id}")
@Operation(summary = "특정 댓글 수정", description = "특정 댓글의 내용을 수정합니다.")
ResponseEntity<BaseResponse<CommentDetailResponse>> updateComment(
@Parameter(description = "댓글 식별자", example = "1") @PathVariable Long id,
@RequestBody @Valid CommentRequest request);

@DeleteMapping("{id}")
@Operation(summary = "특정 댓글 삭제", description = "특정 댓글을 삭제합니다.(Hard Delete)")
ResponseEntity<BaseResponse<Void>> deleteComment(
@Parameter(description = "댓글 식별자", example = "1") @PathVariable Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.controller;

import java.util.List;

import jakarta.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.sku.refit.domain.comment.dto.request.CommentRequest;
import com.sku.refit.domain.comment.dto.response.CommentDetailResponse;
import com.sku.refit.domain.comment.service.CommentService;
import com.sku.refit.global.response.BaseResponse;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequiredArgsConstructor
@Slf4j
public class CommentControllerImpl implements CommentController {

private final CommentService commentService;

@Override
public ResponseEntity<BaseResponse<CommentDetailResponse>> createComment(
@Valid CommentRequest request, @RequestParam Long postId) {

CommentDetailResponse response = commentService.createComment(request, postId);
return ResponseEntity.ok(BaseResponse.success(response));
}

@Override
public ResponseEntity<BaseResponse<List<CommentDetailResponse>>> getAllCommentsByPostId(
@RequestParam Long postId) {

List<CommentDetailResponse> response = commentService.getAllCommentsByPostId(postId);
return ResponseEntity.ok(BaseResponse.success(response));
}

@Override
public ResponseEntity<BaseResponse<CommentDetailResponse>> updateComment(
@PathVariable Long id, @Valid CommentRequest request) {

CommentDetailResponse response = commentService.updateComment(id, request);
return ResponseEntity.ok(BaseResponse.success(response));
}

@Override
public ResponseEntity<BaseResponse<Void>> deleteComment(@PathVariable Long id) {

commentService.deleteComment(id);
return ResponseEntity.ok(BaseResponse.success(null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.dto.request;

import jakarta.validation.constraints.NotBlank;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(title = "CommentRequest DTO", description = "새 댓글 등록을 위한 데이터 전송")
public class CommentRequest {

@NotBlank(message = "댓글 내용은 필수입니다.")
@Schema(description = "댓글 내용", example = "답변 감사합니다~~~")
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.dto.response;

import java.time.LocalDateTime;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@Schema(title = "CommentDetailResponse DTO", description = "댓글 상세 정보 응답 반환")
public class CommentDetailResponse {

@Schema(description = "댓글 식별자", example = "1")
private Long commentId;

@Schema(description = "댓글 내용", example = "처음 가보는거라 질문 드립니다 ㅠㅠ")
private String content;

@Schema(description = "댓글 작성자 여부", example = "true")
private Boolean isWriter;

@Schema(description = "댓글 작성 시간", example = "20250101T120000")
private LocalDateTime createdAt;
}
52 changes: 52 additions & 0 deletions src/main/java/com/sku/refit/domain/comment/entity/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
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 com.sku.refit.domain.post.entity.Post;
import com.sku.refit.domain.user.entity.User;
import com.sku.refit.global.common.BaseTimeEntity;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Table(name = "comment")
public class Comment extends BaseTimeEntity {

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

@Column(nullable = false)
private String content;

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", nullable = false)
private User user;

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "post_id", nullable = false)
private Post post;

public void update(String content) {
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.exception;

import org.springframework.http.HttpStatus;

import com.sku.refit.global.exception.model.BaseErrorCode;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum CommentErrorCode implements BaseErrorCode {
COMMENT_NOT_FOUND("COMMENT001", "댓글이 존재하지 않습니다.", HttpStatus.NOT_FOUND),
;

private final String code;
private final String message;
private final HttpStatus status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.mapper;

import org.springframework.stereotype.Component;

import com.sku.refit.domain.comment.dto.request.CommentRequest;
import com.sku.refit.domain.comment.dto.response.CommentDetailResponse;
import com.sku.refit.domain.comment.entity.Comment;
import com.sku.refit.domain.post.entity.Post;
import com.sku.refit.domain.user.entity.User;

@Component
public class CommentMapper {

public Comment toComment(CommentRequest request, User user, Post post) {
return Comment.builder().content(request.getContent()).user(user).post(post).build();
}

public CommentDetailResponse toDetailResponse(Comment comment, User user) {

return CommentDetailResponse.builder()
.commentId(comment.getId())
.content(comment.getContent())
.isWriter(comment.getUser().getId().equals(user.getId()))
.createdAt(comment.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.sku.refit.domain.comment.entity.Comment;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findAllByPostIdOrderByCreatedAtAsc(Long postId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) SKU 다시입을Lab
*/
package com.sku.refit.domain.comment.service;

import java.util.List;

import com.sku.refit.domain.comment.dto.request.CommentRequest;
import com.sku.refit.domain.comment.dto.response.CommentDetailResponse;

public interface CommentService {

/**
* 새로운 댓글 생성
*
* @param request 댓글 생성 요청 데이터
* @param postId 댓글이 달릴 게시글 ID
* @return 생성된 댓글 상세 정보
*/
CommentDetailResponse createComment(CommentRequest request, Long postId);

/**
* 특정 게시글의 댓글 전체 조회
*
* @param postId 게시글 ID
* @return 댓글 상세 정보 리스트
*/
List<CommentDetailResponse> getAllCommentsByPostId(Long postId);

/**
* 댓글 수정
*
* @param id 댓글 ID
* @param request 수정 요청 데이터
* @return 수정된 댓글 정보
*/
CommentDetailResponse updateComment(Long id, CommentRequest request);

/**
* 댓글 삭제 (Hard Delete)
*
* @param id 댓글 ID
*/
void deleteComment(Long id);
}
Loading