Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8354b54
refactor(*): ResponseCode μΆ”κ°€, SuccessCode, ErrorCode μΈν„°νŽ˜μ΄μŠ€ν™”
kaswhy Dec 10, 2025
feedab9
refactor(global): ErrorMeta μœ„μΉ˜ 이동
kaswhy Dec 10, 2025
8be6ea8
refactor(global): ApiResponseBody μœ„μΉ˜ 이동
kaswhy Dec 10, 2025
377f201
refactor(*): SuccessCode, ErrorCode 도메인별 뢄리
kaswhy Dec 10, 2025
b8e4c25
feat(comment): comment Entity 생성
kaswhy Dec 10, 2025
be5be04
feat(comment): comment κ΄€λ ¨ 컨트둀러, 리포지토리 μŠ€μΌˆλ ˆν†€ μ½”λ“œ μž‘μ„±
kaswhy Dec 11, 2025
53b8b1b
refactor(member): member κ΄€λ ¨ 리포지토리 뢄리
kaswhy Dec 11, 2025
ec7e3a4
refactor(global): ApiResponseBody λ‚΄λΆ€ 컬럼 μˆœμ„œ λ³€κ²½
kaswhy Dec 11, 2025
5cc99d0
refactor(article): article κ΄€λ ¨ 리포지토리 뢄리
kaswhy Dec 11, 2025
496170e
refactor(member): ν•„μš”μ—†λŠ” μ˜μ‘΄μ„± μ‚­μ œ
kaswhy Dec 11, 2025
87bd406
feat(comment): λŒ“κΈ€ μž‘μ„± api 생성
kaswhy Dec 11, 2025
58df82a
feat(comment): λŒ“κΈ€ μˆ˜μ • api 생성
kaswhy Dec 24, 2025
e2d1789
refactor(comment): λŒ“κΈ€ 생성 μœ μŠ€μΌ€μ΄μŠ€ νŠΈλžœμž­μ…˜ 경계 λͺ…μ‹œ
kaswhy Dec 24, 2025
f6af01c
refactor(comment): λŒ“κΈ€ κ΄€λ ¨ μ„œλΉ„μŠ€ 클래슀 넀이밍 동사 λͺ…사 μˆœμ„œ λ³€κ²½
kaswhy Dec 24, 2025
fc88305
refactor(comment): λŒ“κΈ€ κ΄€λ ¨ μ„œλΉ„μŠ€λ‹¨ DTO 넀이밍 동사 λͺ…사 μˆœμ„œ λ³€κ²½
kaswhy Dec 24, 2025
e794fb5
refactor(comment): λŒ“κΈ€ κ΄€λ ¨ μ»¨νŠΈλ‘€λŸ¬λ‹¨ DTO 넀이밍 동사 λͺ…사 μˆœμ„œ λ³€κ²½
kaswhy Dec 24, 2025
cf12624
feat(comment): λŒ“κΈ€ 쑰회 api κ΅¬ν˜„
kaswhy Dec 24, 2025
2d8124b
feat(comment): λŒ“κΈ€ μ‚­μ œ api κ΅¬ν˜„
kaswhy Dec 25, 2025
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
8 changes: 4 additions & 4 deletions src/main/java/org/sopt/article/api/ArticleController.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.sopt.article.api;

import static org.sopt.global.response.SuccessCode.ARTICLE_CREATED_SUCCESS;
import static org.sopt.global.response.SuccessCode.ARTICLE_LIST_RETRIEVED_SUCCESS;
import static org.sopt.global.response.SuccessCode.ARTICLE_RETRIEVED_SUCCESS;
import static org.sopt.article.api.code.ArticleSuccessCode.ARTICLE_CREATED_SUCCESS;
import static org.sopt.article.api.code.ArticleSuccessCode.ARTICLE_LIST_RETRIEVED_SUCCESS;
import static org.sopt.article.api.code.ArticleSuccessCode.ARTICLE_RETRIEVED_SUCCESS;

import lombok.RequiredArgsConstructor;
import org.sopt.article.api.dto.request.ArticleCreateRequest;
Expand All @@ -18,7 +18,7 @@
import org.sopt.article.application.port.in.ArticleFindAllUsecase;
import org.sopt.article.application.port.in.ArticleFindOneUsecase;
import org.sopt.article.application.port.in.ArticleWriteUsecase;
import org.sopt.global.response.ApiResponseBody;
import org.sopt.global.response.dto.ApiResponseBody;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/sopt/article/api/code/ArticleSuccessCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.sopt.article.api.code;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.sopt.global.response.code.SuccessCode;

@Getter
@RequiredArgsConstructor
public enum ArticleSuccessCode implements SuccessCode {

// 200 OK
ARTICLE_RETRIEVED_SUCCESS(200, "ART_200_001", "νŠΉμ • κ²Œμ‹œκΈ€ 쑰회λ₯Ό μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€."),
ARTICLE_LIST_RETRIEVED_SUCCESS(200, "ART_200_002", "κ²Œμ‹œκΈ€ 전체 μ‘°νšŒκ°€ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),

// 201 CREATED
ARTICLE_CREATED_SUCCESS(201, "ART_201_001", "κ²Œμ‹œλ¬Όμ΄ μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€."),

;

private final int status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.article.api.dto.request;

public record ArticleUpdateRequest(
String content
) {

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.sopt.article.application.service;

import static org.sopt.global.exception.ErrorCode.ARTICLE_NOT_FOUND;
import static org.sopt.article.domain.exception.ArticleErrorCode.ARTICLE_NOT_FOUND;

import lombok.RequiredArgsConstructor;
import org.sopt.article.application.dto.result.ArticleFindOneResult;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sopt.article.application.service;

import static org.sopt.global.exception.ErrorCode.ARTICLE_BY_NAME_ALREADY_EXISTS;
import static org.sopt.global.exception.ErrorCode.MEMBER_NOT_FOUND;
import static org.sopt.article.domain.exception.ArticleErrorCode.ARTICLE_BY_NAME_ALREADY_EXISTS;
import static org.sopt.member.domain.exception.MemberErrorCode.MEMBER_NOT_FOUND;

import lombok.RequiredArgsConstructor;
import org.sopt.article.application.dto.command.ArticleWriteCommand;
Expand All @@ -12,22 +12,22 @@
import org.sopt.article.domain.port.out.ArticleRepository;
import org.sopt.member.domain.exception.MemberException;
import org.sopt.member.domain.model.Member;
import org.sopt.member.domain.port.out.MemberRepositoryPort;
import org.sopt.member.domain.port.out.MemberRepository;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class ArticleWriteService implements ArticleWriteUsecase {

private final MemberRepositoryPort memberRepositoryPort;
private final MemberRepository memberRepository;
private final ArticleRepository articleRepository;

public ArticleCreateResult writeArticle(ArticleWriteCommand articleWriteCommand) {
if(articleRepository.existsByTitle(articleWriteCommand.title())) {
throw new ArticleException(ARTICLE_BY_NAME_ALREADY_EXISTS);
}

Member member = memberRepositoryPort.findById(articleWriteCommand.memberId())
Member member = memberRepository.findById(articleWriteCommand.memberId())
.orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND));
Article article = Article.create(
member,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.sopt.article.domain.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.sopt.global.exception.ErrorCode;

@Getter
@RequiredArgsConstructor
public enum ArticleErrorCode implements ErrorCode {

// 404 NOT FOUND
ARTICLE_NOT_FOUND(404, "ART_404_001", "μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ²Œμ‹œλ¬Όμž…λ‹ˆλ‹€."),

// 409 CONFLICT
ARTICLE_BY_NAME_ALREADY_EXISTS(409, "ART_409_001", "ν•΄λ‹Ή 제λͺ©μœΌλ‘œ μž‘μ„±λœ κ²Œμ‹œκΈ€μ΄ 이미 μ‘΄μž¬ν•©λ‹ˆλ‹€.");

private final int status;
private final String code;
private final String message;

}
2 changes: 1 addition & 1 deletion src/main/java/org/sopt/article/domain/model/Article.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class Article extends BaseEntity {
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "memberId")
@JoinColumn(name = "member_id")
private Member member;

@Enumerated(EnumType.STRING)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.sopt.article.domain.port.out;

import java.util.List;
import java.util.Optional;
import org.sopt.article.domain.model.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {
public interface ArticleRepository {

Article save(Article article);
boolean existsByTitle(String title);
Optional<Article> findById(Long id);
List<Article> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.article.infrastructure.jpa;

import org.sopt.article.domain.model.Article;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ArticleJpaRepository extends JpaRepository<Article, Long> {

boolean existsByTitle(String title);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.sopt.article.infrastructure.jpa;

import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.sopt.article.domain.model.Article;
import org.sopt.article.domain.port.out.ArticleRepository;
import org.springframework.stereotype.Repository;

@RequiredArgsConstructor
@Repository
public class ArticleRepositoryAdapter implements ArticleRepository {

private final ArticleJpaRepository jpa;

@Override
public Article save(Article article) {
return jpa.save(article);
}

@Override
public boolean existsByTitle(String title){
return jpa.existsByTitle(title);
}

@Override
public Optional<Article> findById(Long id) {
return jpa.findById(id);
}

@Override
public List<Article> findAll() {
return jpa.findAll();
}

}
119 changes: 119 additions & 0 deletions src/main/java/org/sopt/comment/api/CommentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.sopt.comment.api;

import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_CREATED_SUCCESS;
import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_DELETE_SUCCESS;
import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_LIST_RETRIEVED_SUCCESS;
import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_UPDATED_SUCCESS;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.sopt.comment.api.dto.request.CreateCommentRequest;
import org.sopt.comment.api.dto.request.UpdateCommentRequest;
import org.sopt.comment.api.dto.response.CreateCommentResponse;
import org.sopt.comment.api.dto.response.GetCommentListResponse;
import org.sopt.comment.api.dto.response.GetCommentMetaResponse;
import org.sopt.comment.api.dto.response.UpdateCommentResponse;
import org.sopt.comment.api.mapper.CommentRequestMapper;
import org.sopt.comment.api.mapper.CommentResponseMapper;
import org.sopt.comment.application.dto.command.CreateCommentCommand;
import org.sopt.comment.application.dto.command.UpdateCommentCommand;
import org.sopt.comment.application.dto.result.CreateCommentResult;
import org.sopt.comment.application.dto.result.GetCommentListResult;
import org.sopt.comment.application.dto.result.UpdateCommentResult;
import org.sopt.comment.application.port.in.CreateCommentUsecase;
import org.sopt.comment.application.port.in.DeleteCommentUsecase;
import org.sopt.comment.application.port.in.GetCommentListUsecase;
import org.sopt.comment.application.port.in.UpdateCommentUsecase;
import org.sopt.global.response.dto.ApiResponseBody;
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.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api/v1")
@RequiredArgsConstructor
@RestController
public class CommentController {

private final CreateCommentUsecase createCommentUsecase;
private final UpdateCommentUsecase updateCommentUsecase;
private final GetCommentListUsecase getCommentListUsecase;
private final DeleteCommentUsecase deleteCommentUsecase;

@PostMapping("/articles/{articleId}/comments")
public ResponseEntity<ApiResponseBody<CreateCommentResponse, Void>> createComment(
@RequestHeader Long userId,
@PathVariable Long articleId,
@Valid @RequestBody CreateCommentRequest createCommentRequest
) {
CreateCommentCommand createCommentCommand = CommentRequestMapper
.toCommentCreateCommand(userId, articleId, createCommentRequest);
CreateCommentResult commentCreateResult = createCommentUsecase
.writeComment(createCommentCommand);
CreateCommentResponse createCommentResponse = CommentResponseMapper
.toCreateResponse(commentCreateResult);

return ResponseEntity.status(CREATED)
.body(ApiResponseBody.created(COMMENT_CREATED_SUCCESS, createCommentResponse));
}

@GetMapping("/articles/{articleId}/comments")
public ResponseEntity<ApiResponseBody<GetCommentListResponse, GetCommentMetaResponse>> getCommentList(
@PathVariable Long articleId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
GetCommentListResult getCommentListResult = getCommentListUsecase
.findByArticleId(articleId, page, size);
GetCommentListResponse getCommentListResponse = CommentResponseMapper.toGetListResponse(
getCommentListResult
);
GetCommentMetaResponse getCommentMetaResponse = GetCommentMetaResponse
.create(getCommentListResult.getCommentMetaResult());

return ResponseEntity.status(OK)
.body(ApiResponseBody.ok(
COMMENT_LIST_RETRIEVED_SUCCESS,
getCommentListResponse,
getCommentMetaResponse
));
}

@PatchMapping("/comments/{commentId}")
public ResponseEntity<ApiResponseBody<UpdateCommentResponse, Void>> updateComment(
@RequestHeader Long userId,
@PathVariable Long commentId,
@Valid @RequestBody UpdateCommentRequest updateCommentRequest
) {
UpdateCommentCommand updateCommentCommand = CommentRequestMapper
.toCommentUpdateCommand(userId, commentId, updateCommentRequest);
UpdateCommentResult commentUpdateResult = updateCommentUsecase
.updateComment(updateCommentCommand);
UpdateCommentResponse updateCommentResponse = CommentResponseMapper
.toUpdateResponse(commentUpdateResult);

return ResponseEntity.status(OK)
.body(ApiResponseBody.ok(COMMENT_UPDATED_SUCCESS, updateCommentResponse));
}

@DeleteMapping("/comments/{commentId}")
public ResponseEntity<ApiResponseBody<Void, Void>> deleteComment(
@RequestHeader Long userId,
@PathVariable Long commentId
) {
deleteCommentUsecase.deleteComment(userId, commentId);

return ResponseEntity.status(OK)
.body(ApiResponseBody.ok(COMMENT_DELETE_SUCCESS));
}

}
25 changes: 25 additions & 0 deletions src/main/java/org/sopt/comment/api/code/CommentSuccessCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.sopt.comment.api.code;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.sopt.global.response.code.SuccessCode;

@Getter
@RequiredArgsConstructor
public enum CommentSuccessCode implements SuccessCode {

// 200 OK
COMMENT_UPDATED_SUCCESS(200, "COMMENT_200_001", "μ„±κ³΅μ μœΌλ‘œ λŒ“κΈ€μ„ μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€."),
COMMENT_LIST_RETRIEVED_SUCCESS(200, "COMMENT_200_002", "μ„±κ³΅μ μœΌλ‘œ ν•΄λ‹Ή κ²Œμ‹œλ¬Όμ˜ λŒ“κΈ€ λͺ©λ‘μ„ μ‘°νšŒν–ˆμŠ΅λ‹ˆλ‹€."),
COMMENT_DELETE_SUCCESS(200, "COMMENT_200_003", "μ„±κ³΅μ μœΌλ‘œ λŒ“κΈ€μ„ μ‚­μ œν–ˆμŠ΅λ‹ˆλ‹€."),

// 201 CREATED
COMMENT_CREATED_SUCCESS(201, "COMMENT_201_001", "μ„±κ³΅μ μœΌλ‘œ λŒ“κΈ€μ„ μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€."),

;

private final int status;
private final String code;
private final String message;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.comment.api.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;

public record CreateCommentRequest(
@NotBlank
@Max(300)
String content
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.comment.api.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;

public record UpdateCommentRequest(
@NotBlank
@Max(300)
String content
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.comment.api.dto.response;

public record CreateCommentResponse(
Long commentId
){

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.comment.api.dto.response;

import org.sopt.comment.application.dto.result.GetCommentAuthorResult;

public record GetCommentAuthorResponse(
Long id,
String name
) {

public static GetCommentAuthorResponse create(GetCommentAuthorResult getCommentAuthorResult) {
return new GetCommentAuthorResponse(
getCommentAuthorResult.id(),
getCommentAuthorResult.name()
);
}

}
Loading