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
@@ -1,8 +1,9 @@
[[Tech-Article-Comments-Register]]
== 기술블로그 댓글 작성 API(POST: /devdevdev/api/v1/articles/{techArticleId}/comments)

* 회원은 기술블로그에 댓글을 작성할 수 있다.
* 익명회원은 댓글을 작성할 수 없다.
* 기술블로그에 댓글을 작성할 수 있다.
** 회원인 경우 토큰을 `Authorization` 헤더에 포함시켜야 한다.
** 익명 회원인 경우 `Anonymous-Member-Id` 헤더에 익명 회원 아이디를 포함시켜야 한다.

=== 정상 요청/응답

Expand Down Expand Up @@ -36,10 +37,7 @@ include::{snippets}/tech-article-comments/response-fields.adoc[]

* `댓글 내용을 작성해주세요.`: 댓글(contents)을 작성하지 않는 경우(공백 이거나 빈문자열)
* `회원을 찾을 수 없습니다.`: 회원 정보가 없을 경우
* `익명 회원은 사용할 수 없는 기능 입니다.`: 익명 회원이 사용할 수 없는 기능일 경우
* `존재하지 않는 기술블로그입니다.`: 기술블로그가 존재하지 않는 경우
* `익명 사용자가 아닙니다. 잘못된 메소드 호출 입니다.`: 회원이 익명 회원 메소드를 호출한 경우

include::{snippets}/tech-article-comments-anonymous-exception/response-body.adoc[]
include::{snippets}/tech-article-comments-not-found-member-exception/response-body.adoc[]
include::{snippets}/tech-article-comments-not-found-tech-article-exception/response-body.adoc[]
include::{snippets}/tech-article-comments-null-exception/response-body.adoc[]
include::{snippets}/tech-article-comments-not-found-member-exception/response-body.adoc[]
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[[Tech-Article-Reply-Register]]
== 기술블로그 답글 작성 API(POST: /devdevdev/api/v1/articles/{techArticleId}/comments/{originParentTechCommentId}/{parentTechCommentId}

* 회원은 기술블로그에 댓글에 답글을 작성할 수 있다.
* 익명회원은 답글을 작성할 수 없다.
* 기술블로그에 댓글에 답글을 작성할 수 있다.
** 회원인 경우 토큰을 `Authorization` 헤더에 포함시켜야 한다.
** 익명 회원인 경우 `Anonymous-Member-Id` 헤더에 익명 회원 아이디를 포함시켜야 한다.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

댓글 작성 문서에도 위 내용을 적어주면 좋을 것 같아요!

* 삭제된 댓글에는 답글을 작성할 수 없다.
* 최초 댓글에 대한 답글을 작성할 경우 `techCommentOriginParentId` 값과 `techParentCommentId` 값이 동일하다.

Expand Down Expand Up @@ -40,5 +41,6 @@ include::{snippets}/register-tech-article-reply/response-fields.adoc[]
* `회원을 찾을 수 없습니다.`: 회원 정보가 없을 경우
* `익명 회원은 사용할 수 없는 기능 입니다.`: 익명 회원이 사용할 수 없는 기능일 경우
* `존재하지 않는 기술블로그입니다.`: 기술블로그가 존재하지 않는 경우
* `익명 사용자가 아닙니다. 잘못된 메소드 호출 입니다.`: 회원이 익명 회원 메소드를 호출한 경우

include::{snippets}/register-tech-article-reply-null-exception/response-body.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ private TechComment(CommentContents contents, Count blameTotalCount, Count recom
this.deletedAt = deletedAt;
}

public static TechComment createMainTechComment(CommentContents contents, Member createdBy,
TechArticle techArticle) {
public static TechComment createMainTechCommentByMember(CommentContents contents, Member createdBy,
TechArticle techArticle) {
return TechComment.builder()
.contents(contents)
.createdBy(createdBy)
Expand All @@ -125,9 +125,21 @@ public static TechComment createMainTechComment(CommentContents contents, Member
.build();
}

public static TechComment createRepliedTechComment(CommentContents contents, Member createdBy,
TechArticle techArticle, TechComment originParent,
TechComment parent) {
public static TechComment createMainTechCommentByAnonymousMember(CommentContents contents, AnonymousMember createdAnonymousBy,
TechArticle techArticle) {
return TechComment.builder()
.contents(contents)
.createdAnonymousBy(createdAnonymousBy)
.techArticle(techArticle)
.blameTotalCount(Count.defaultCount())
.recommendTotalCount(Count.defaultCount())
.replyTotalCount(Count.defaultCount())
.build();
}

public static TechComment createRepliedTechCommentByMember(CommentContents contents, Member createdBy,
TechArticle techArticle, TechComment originParent,
TechComment parent) {
return TechComment.builder()
.contents(contents)
.createdBy(createdBy)
Expand All @@ -140,6 +152,22 @@ public static TechComment createRepliedTechComment(CommentContents contents, Mem
.build();
}

public static TechComment createRepliedTechCommentByAnonymousMember(CommentContents contents,
AnonymousMember createdAnonymousBy,
TechArticle techArticle, TechComment originParent,
TechComment parent) {
return TechComment.builder()
.contents(contents)
.createdAnonymousBy(createdAnonymousBy)
.techArticle(techArticle)
.blameTotalCount(Count.defaultCount())
.recommendTotalCount(Count.defaultCount())
.replyTotalCount(Count.defaultCount())
.originParent(originParent)
.parent(parent)
.build();
}

public void changeDeletedAt(LocalDateTime deletedAt, Member deletedBy) {
this.deletedAt = deletedAt;
this.deletedBy = deletedBy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.GuestTechArticleService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.MemberTechArticleService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.TechArticleService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techComment.GuestTechCommentService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techComment.GuestTechCommentServiceV2;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techComment.MemberTechCommentService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techComment.TechCommentService;
import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils;
Expand All @@ -29,7 +29,7 @@ public TechArticleService getTechArticleService() {

public TechCommentService getTechCommentService() {
if (AuthenticationMemberUtils.isAnonymous()) {
return applicationContext.getBean(GuestTechCommentService.class);
return applicationContext.getBean(GuestTechCommentServiceV2.class);
}
return applicationContext.getBean(MemberTechCommentService.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.dreamypatisiel.devdevdev.domain.service.techArticle.dto;

import com.dreamypatisiel.devdevdev.web.dto.request.techArticle.RegisterTechCommentRequest;
import lombok.Data;

@Data
public class TechCommentDto {
private String anonymousMemberId;
private String contents;

public static TechCommentDto createRegisterCommentDto(RegisterTechCommentRequest registerTechCommentRequest,
String anonymousMemberId) {
TechCommentDto techCommentDto = new TechCommentDto();
techCommentDto.setContents(registerTechCommentRequest.getContents());
techCommentDto.setAnonymousMemberId(anonymousMemberId);
return techCommentDto;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import static com.dreamypatisiel.devdevdev.domain.exception.GuestExceptionMessage.INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE;

import com.dreamypatisiel.devdevdev.domain.policy.TechArticlePopularScorePolicy;
import com.dreamypatisiel.devdevdev.domain.policy.TechBestCommentsPolicy;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentRepository;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentSort;
import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.dto.TechCommentDto;
import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils;
import com.dreamypatisiel.devdevdev.web.dto.SliceCommentCustom;
import com.dreamypatisiel.devdevdev.web.dto.request.techArticle.ModifyTechCommentRequest;
import com.dreamypatisiel.devdevdev.web.dto.request.techArticle.RegisterTechCommentRequest;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentRecommendResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentsResponse;
Expand All @@ -24,26 +24,23 @@
@Transactional(readOnly = true)
public class GuestTechCommentService extends TechCommentCommonService implements TechCommentService {

private final AnonymousMemberService anonymousMemberService;

public GuestTechCommentService(TechCommentRepository techCommentRepository,
TechBestCommentsPolicy techBestCommentsPolicy,
AnonymousMemberService anonymousMemberService) {
super(techCommentRepository, techBestCommentsPolicy);
this.anonymousMemberService = anonymousMemberService;
TechArticlePopularScorePolicy techArticlePopularScorePolicy) {
super(techCommentRepository, techBestCommentsPolicy, techArticlePopularScorePolicy);
}

@Override
public TechCommentResponse registerMainTechComment(Long techArticleId,
RegisterTechCommentRequest registerTechCommentRequest,
TechCommentDto techCommentDto,
Authentication authentication) {
throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE);
}

@Override
public TechCommentResponse registerRepliedTechComment(Long techArticleId, Long originParentTechCommentId,
Long parentTechCommentId,
RegisterTechCommentRequest registerRepliedTechCommentRequest,
TechCommentDto registerRepliedTechCommentRequest,
Authentication authentication) {
throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package com.dreamypatisiel.devdevdev.domain.service.techArticle.techComment;

import static com.dreamypatisiel.devdevdev.domain.exception.GuestExceptionMessage.INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE;
import static com.dreamypatisiel.devdevdev.domain.exception.TechArticleExceptionMessage.INVALID_NOT_FOUND_TECH_COMMENT_MESSAGE;

import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember;
import com.dreamypatisiel.devdevdev.domain.entity.TechArticle;
import com.dreamypatisiel.devdevdev.domain.entity.TechComment;
import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents;
import com.dreamypatisiel.devdevdev.domain.policy.TechArticlePopularScorePolicy;
import com.dreamypatisiel.devdevdev.domain.policy.TechBestCommentsPolicy;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentRepository;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentSort;
import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.dto.TechCommentDto;
import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.TechArticleCommonService;
import com.dreamypatisiel.devdevdev.exception.NotFoundException;
import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils;
import com.dreamypatisiel.devdevdev.web.dto.SliceCommentCustom;
import com.dreamypatisiel.devdevdev.web.dto.request.techArticle.ModifyTechCommentRequest;
import com.dreamypatisiel.devdevdev.web.dto.request.techArticle.RegisterTechCommentRequest;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentRecommendResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentsResponse;
Expand All @@ -26,27 +33,84 @@
public class GuestTechCommentServiceV2 extends TechCommentCommonService implements TechCommentService {

private final AnonymousMemberService anonymousMemberService;
private final TechArticleCommonService techArticleCommonService;

public GuestTechCommentServiceV2(TechCommentRepository techCommentRepository,
TechBestCommentsPolicy techBestCommentsPolicy,
AnonymousMemberService anonymousMemberService) {
super(techCommentRepository, techBestCommentsPolicy);
public GuestTechCommentServiceV2(TechCommentRepository techCommentRepository, TechBestCommentsPolicy techBestCommentsPolicy,
AnonymousMemberService anonymousMemberService,
TechArticlePopularScorePolicy techArticlePopularScorePolicy,
TechArticleCommonService techArticleCommonService) {
super(techCommentRepository, techBestCommentsPolicy, techArticlePopularScorePolicy);
this.anonymousMemberService = anonymousMemberService;
this.techArticleCommonService = techArticleCommonService;
}

@Override
public TechCommentResponse registerMainTechComment(Long techArticleId,
RegisterTechCommentRequest registerTechCommentRequest,
@Transactional
public TechCommentResponse registerMainTechComment(Long techArticleId, TechCommentDto registerTechCommentDto,
Authentication authentication) {
throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE);

// 익명 회원인지 검증
AuthenticationMemberUtils.validateAnonymousMethodCall(authentication);

String anonymousMemberId = registerTechCommentDto.getAnonymousMemberId();
String contents = registerTechCommentDto.getContents();

// 회원 조회 또는 생성
AnonymousMember findAnonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId);

// 기술블로그 조회
TechArticle techArticle = techArticleCommonService.findTechArticle(techArticleId);

// 댓글 엔티티 생성 및 저장
TechComment techComment = TechComment.createMainTechCommentByAnonymousMember(new CommentContents(contents),
findAnonymousMember, techArticle);
techCommentRepository.save(techComment);

// 기술블로그 댓글수 증가
techArticle.incrementCommentCount();

// 데이터 가공
return new TechCommentResponse(techComment.getId());
}

@Override
@Transactional
public TechCommentResponse registerRepliedTechComment(Long techArticleId, Long originParentTechCommentId,
Long parentTechCommentId,
RegisterTechCommentRequest registerRepliedTechCommentRequest,
TechCommentDto registerRepliedTechCommentDto,
Authentication authentication) {
throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE);
// 익명 회원인지 검증
AuthenticationMemberUtils.validateAnonymousMethodCall(authentication);

String anonymousMemberId = registerRepliedTechCommentDto.getAnonymousMemberId();
String contents = registerRepliedTechCommentDto.getContents();

// 회원 조회 또는 생성
AnonymousMember findAnonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId);

// 답글 대상의 기술블로그 댓글 조회
TechComment findParentTechComment = techCommentRepository.findWithTechArticleByIdAndTechArticleId(
parentTechCommentId, techArticleId)
.orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_TECH_COMMENT_MESSAGE));

// 답글 엔티티 생성 및 저장
TechComment findOriginParentTechComment = super.getAndValidateOriginParentTechComment(originParentTechCommentId,
findParentTechComment);
TechArticle findTechArticle = findParentTechComment.getTechArticle();

TechComment repliedTechComment = TechComment.createRepliedTechCommentByAnonymousMember(new CommentContents(contents),
findAnonymousMember, findTechArticle, findOriginParentTechComment, findParentTechComment);
techCommentRepository.save(repliedTechComment);

// 아티클의 댓글수 증가
findTechArticle.incrementCommentCount();
findTechArticle.changePopularScore(techArticlePopularScorePolicy);

// origin 댓글의 답글수 증가
findOriginParentTechComment.incrementReplyTotalCount();

// 데이터 가공
return new TechCommentResponse(repliedTechComment.getId());
}

@Override
Expand Down Expand Up @@ -94,6 +158,7 @@ public TechCommentRecommendResponse recommendTechComment(Long techArticleId, Lon
* @Since: 2025.07.20
*/
@Override
@Transactional
public List<TechCommentsResponse> findTechBestComments(int size, Long techArticleId,
String anonymousMemberId, Authentication authentication) {

Expand Down
Loading