Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| 코호트 / 파일(s) | 설명 |
|---|---|
엔티티 및 DTO backend/src/main/java/moadong/club/entity/PromotionArticle.java, backend/src/main/java/moadong/club/payload/dto/PromotionArticleDto.java, backend/src/main/java/moadong/club/payload/request/PromotionArticleCreateRequest.java, backend/src/main/java/moadong/club/payload/response/PromotionArticleResponse.java |
MongoDB 문서 엔티티 및 요청/응답 데이터 모델 추가. PromotionArticle은 clubId, 제목, 위치, 이벤트 기간, 설명, 이미지 목록을 포함하고, 생성일시는 기본값으로 설정됨. 요청 DTO는 필드 유효성 검사 포함. |
리포지토리 backend/src/main/java/moadong/club/repository/PromotionArticleRepository.java |
MongoDB 리포지토리 인터페이스 추가. 전체 홍보 게시물 조회 및 clubId 기반 필터링 메서드 포함. 조회 결과는 생성일시 역순으로 정렬됨. |
서비스 backend/src/main/java/moadong/club/service/PromotionArticleService.java |
비즈니스 로직 서비스 추가. 홍보 게시물 조회 및 생성 기능 구현. 생성 시 clubId 변환, 클럽 존재 여부 검증, 게시물 저장 처리. |
컨트롤러 backend/src/main/java/moadong/club/controller/PromotionArticleController.java |
REST API 엔드포인트 추가. GET /api/promotion (전체 조회), POST /api/promotion (생성, 인증 필수). Swagger/OpenAPI 주석 및 보안 요구사항 포함. |
Sequence Diagram(s)
sequenceDiagram
actor Client
participant Controller as PromotionArticleController
participant Service as PromotionArticleService
participant ClubRepo as ClubRepository
participant PromotionRepo as PromotionArticleRepository
Client->>Controller: POST /api/promotion<br/>(PromotionArticleCreateRequest)
Controller->>Service: createPromotionArticle(request)
Service->>ClubRepo: findById(clubId)
alt Club exists
ClubRepo-->>Service: Club entity
Service->>PromotionRepo: save(PromotionArticle)
PromotionRepo-->>Service: saved entity
Service-->>Controller: void
Controller-->>Client: 200 OK
else Club not found
ClubRepo-->>Service: empty
Service-->>Controller: RestApiException(CLUB_NOT_FOUND)
Controller-->>Client: 400/404 error
end
sequenceDiagram
actor Client
participant Controller as PromotionArticleController
participant Service as PromotionArticleService
participant PromotionRepo as PromotionArticleRepository
Client->>Controller: GET /api/promotion
Controller->>Service: getPromotionArticles()
Service->>PromotionRepo: findAllProjectedBy()
PromotionRepo-->>Service: List<PromotionArticleDto>
Service->>Service: wrap in PromotionArticleResponse
Service-->>Controller: PromotionArticleResponse
Controller-->>Client: 200 OK (JSON response)
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related issues
- MOA-585 "홍보 게시판 CR API 추가": 이 PR은 요청된 CREATE 및 READ API를 모두 구현하며, 홍보 게시판의 컨트롤러, 서비스, 리포지토리, 엔티티 및 DTO를 완벽하게 제공합니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | PR 제목 '[feat] 홍보게시판 API 구현'은 변경 사항의 핵심을 명확하게 요약하고 있으며, 추가된 모든 파일과 엔드포인트가 홍보게시판 API 구현과 직접 관련되어 있습니다. |
| Linked Issues check | ✅ Passed | PR은 MOA-585 이슈의 모든 요구사항을 충족합니다: CREATE API [POST /api/promotion]와 READ API [GET /api/promotion] 모두 구현되었으며, MongoDB 기반 데이터 영속성과 필요한 모든 엔티티, DTO, 리포지토리, 서비스가 포함되어 있습니다. |
| Out of Scope Changes check | ✅ Passed | 모든 변경 사항은 홍보게시판 API 구현이라는 PR 범위 내에 있으며, 추가된 모든 파일(PromotionArticleController, PromotionArticle, 관련 DTO, 리포지토리, 서비스)은 직접적으로 CREATE/READ API 기능에 필요한 구성 요소들입니다. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/#1122-event-bbs-cr-api-MOA-585
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
Test Results75 tests 70 ✅ 14s ⏱️ For more details on these failures, see this check. Results for commit 6d65a11. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@backend/src/main/java/moadong/club/payload/request/PromotionArticleCreateRequest.java`:
- Around line 14-15: Add validation to ensure eventEndDate is not before
eventStartDate; in the service method
PromotionArticleService.createPromotionArticle() check request.eventEndDate()
against request.eventStartDate() and throw a RestApiException (e.g.,
ErrorCode.INVALID_DATE_RANGE) when end is before start, or implement an
equivalent custom validator on PromotionArticleCreateRequest that enforces the
same rule before persisting.
In
`@backend/src/main/java/moadong/club/repository/PromotionArticleRepository.java`:
- Line 17: The repository interface PromotionArticleRepository currently
declares an unused method findByClubIdOrderByCreatedAtDesc; remove this unused
method from PromotionArticleRepository to avoid dead API surface (or if it's
intended for future use, add a short Javadoc above
findByClubIdOrderByCreatedAtDesc explaining the planned usage and a TODO with a
ticket/issue reference). Ensure any callers are updated if you choose removal
and run tests/compile to verify no references remain.
In `@backend/src/main/java/moadong/club/service/PromotionArticleService.java`:
- Around line 33-36: The createPromotionArticle method currently only looks up
Club by ID; add an authorization check to ensure the authenticated user is a
member of that Club before creating the article: after converting
request.clubId() and loading Club via clubRepository.findClubById(...) (as
shown) obtain the current user's id (via your existing auth helper/service or
SecurityContext), then verify membership (e.g., check Club.getMembers() or call
membershipRepository.existsByClubIdAndUserId(...)); if the check fails throw a
RestApiException with an appropriate ErrorCode (e.g., ErrorCode.FORBIDDEN or a
new CLUB_MEMBERSHIP_REQUIRED) to block creation. Ensure you reference
createPromotionArticle, PromotionArticleCreateRequest, clubRepository, Club and
RestApiException when implementing.
🧹 Nitpick comments (5)
backend/src/main/java/moadong/club/entity/PromotionArticle.java (2)
23-23:clubId필드에 인덱스 추가를 고려하세요.
PromotionArticleRepository에서findByClubIdOrderByCreatedAtDesc(String clubId)메서드를 사용하고 있습니다.clubId로 검색하는 쿼리 성능 최적화를 위해 인덱스 추가를 권장합니다.♻️ 인덱스 추가 제안
+import org.springframework.data.mongodb.core.index.Indexed; + `@Document`("promotion_articles") `@Getter` `@Builder` `@AllArgsConstructor` `@NoArgsConstructor` public class PromotionArticle { `@Id` private String id; + `@Indexed` private String clubId;
37-37:images필드의 null 안전성을 고려하세요.
images필드가 Builder를 통해 설정되지 않으면null이 될 수 있습니다. API 응답에서 예기치 않은 null 값을 방지하려면 빈 리스트로 기본값을 설정하는 것이 좋습니다.♻️ 기본값 설정 제안
+import java.util.ArrayList; + `@Builder.Default` - private List<String> images; + private List<String> images = new ArrayList<>();backend/src/main/java/moadong/club/repository/PromotionArticleRepository.java (1)
14-15: 페이지네이션 추가를 고려하세요.
findAllProjectedBy()메서드가 모든 게시글을 반환합니다. 데이터가 많아지면 성능 문제가 발생할 수 있으므로,Pageable파라미터를 추가하여 페이지네이션을 지원하는 것을 권장합니다.♻️ 페이지네이션 지원 메서드 추가 예시
+import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + `@Repository` public interface PromotionArticleRepository extends MongoRepository<PromotionArticle, String> { `@Query`(value = "{}", sort = "{ 'createdAt': -1 }") List<PromotionArticleDto> findAllProjectedBy(); + `@Query`(value = "{}") + Page<PromotionArticleDto> findAllProjectedBy(Pageable pageable); + List<PromotionArticle> findByClubIdOrderByCreatedAtDesc(String clubId); }backend/src/main/java/moadong/club/service/PromotionArticleService.java (1)
27-30: 읽기 전용 트랜잭션 설정을 고려하세요.
getPromotionArticles()메서드는 읽기 전용 작업이므로@Transactional(readOnly = true)를 추가하면 성능 최적화에 도움이 됩니다.♻️ 읽기 전용 트랜잭션 추가
+ `@Transactional`(readOnly = true) public PromotionArticleResponse getPromotionArticles() { List<PromotionArticleDto> articles = promotionArticleRepository.findAllProjectedBy(); return new PromotionArticleResponse(articles); }backend/src/main/java/moadong/club/controller/PromotionArticleController.java (1)
35-43: 리소스 생성 시 HTTP 201 Created 응답을 고려하세요.현재
Response.ok()는 HTTP 200을 반환합니다. REST 규칙에 따르면 리소스 생성 성공 시 HTTP 201 Created를 반환하는 것이 더 적절합니다.♻️ HTTP 201 응답으로 변경 예시
public ResponseEntity<?> createPromotionArticle( `@RequestBody` `@Validated` PromotionArticleCreateRequest request) { promotionArticleService.createPromotionArticle(request); - return Response.ok("홍보 게시글이 생성되었습니다."); + return Response.created("홍보 게시글이 생성되었습니다."); }
Response클래스에created()메서드가 없다면:- return Response.ok("홍보 게시글이 생성되었습니다."); + return ResponseEntity.status(HttpStatus.CREATED).body("홍보 게시글이 생성되었습니다.");
| @NotNull Instant eventStartDate, | ||
| @NotNull Instant eventEndDate, |
There was a problem hiding this comment.
eventEndDate가 eventStartDate 이후인지 검증이 필요합니다.
현재 두 날짜 필드가 독립적으로 검증됩니다. 이벤트 종료일이 시작일보다 이전인 경우를 방지하기 위해 커스텀 검증 로직이나 서비스 레이어에서 검증을 추가하는 것을 권장합니다.
🛡️ 서비스 레이어 검증 추가 예시
PromotionArticleService.createPromotionArticle() 메서드에 다음 검증을 추가할 수 있습니다:
if (request.eventEndDate().isBefore(request.eventStartDate())) {
throw new RestApiException(ErrorCode.INVALID_DATE_RANGE);
}🤖 Prompt for AI Agents
In
`@backend/src/main/java/moadong/club/payload/request/PromotionArticleCreateRequest.java`
around lines 14 - 15, Add validation to ensure eventEndDate is not before
eventStartDate; in the service method
PromotionArticleService.createPromotionArticle() check request.eventEndDate()
against request.eventStartDate() and throw a RestApiException (e.g.,
ErrorCode.INVALID_DATE_RANGE) when end is before start, or implement an
equivalent custom validator on PromotionArticleCreateRequest that enforces the
same rule before persisting.
| @Query(value = "{}", sort = "{ 'createdAt': -1 }") | ||
| List<PromotionArticleDto> findAllProjectedBy(); | ||
|
|
||
| List<PromotionArticle> findByClubIdOrderByCreatedAtDesc(String clubId); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "findByClubIdOrderByCreatedAtDesc" --type javaRepository: Moadong/moadong
Length of output: 215
🏁 Script executed:
rg -n "PromotionArticleRepository" --type java -A 3 -B 1Repository: Moadong/moadong
Length of output: 1864
🏁 Script executed:
cat backend/src/main/java/moadong/club/service/PromotionArticleService.javaRepository: Moadong/moadong
Length of output: 2029
findByClubIdOrderByCreatedAtDesc 메서드는 현재 코드베이스에서 사용되지 않습니다.
리포지토리 전체를 검색한 결과, 이 메서드는 어디에서도 호출되지 않는 것으로 확인되었습니다. 메서드 정의 자체만 존재하고 실제 사용처가 없으므로, 이 메서드를 제거하거나 향후 기능에 대한 명확한 계획이 있다면 그 의도를 문서화해야 합니다.
🤖 Prompt for AI Agents
In
`@backend/src/main/java/moadong/club/repository/PromotionArticleRepository.java`
at line 17, The repository interface PromotionArticleRepository currently
declares an unused method findByClubIdOrderByCreatedAtDesc; remove this unused
method from PromotionArticleRepository to avoid dead API surface (or if it's
intended for future use, add a short Javadoc above
findByClubIdOrderByCreatedAtDesc explaining the planned usage and a TODO with a
ticket/issue reference). Ensure any callers are updated if you choose removal
and run tests/compile to verify no references remain.
| public void createPromotionArticle(PromotionArticleCreateRequest request) { | ||
| ObjectId clubObjectId = ObjectIdConverter.convertString(request.clubId()); | ||
| Club club = clubRepository.findClubById(clubObjectId) | ||
| .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); |
There was a problem hiding this comment.
인증된 사용자가 해당 동아리의 멤버인지 확인이 필요합니다.
현재 구현에서는 인증된 사용자라면 어떤 clubId로든 게시글을 생성할 수 있습니다. 사용자가 실제로 해당 동아리에 소속되어 있는지 검증하는 권한 체크가 없으면, 다른 동아리를 사칭한 게시글 생성이 가능합니다.
🛡️ 권한 검증 추가 예시
+import org.springframework.security.core.context.SecurityContextHolder;
+
`@Transactional`
-public void createPromotionArticle(PromotionArticleCreateRequest request) {
+public void createPromotionArticle(PromotionArticleCreateRequest request) {
ObjectId clubObjectId = ObjectIdConverter.convertString(request.clubId());
Club club = clubRepository.findClubById(clubObjectId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
+
+ // 현재 사용자가 해당 동아리에 소속되어 있는지 확인
+ String currentUserId = SecurityContextHolder.getContext().getAuthentication().getName();
+ // User의 clubId와 request.clubId() 비교 로직 추가
+ if (!isUserBelongsToClub(currentUserId, request.clubId())) {
+ throw new RestApiException(ErrorCode.UNAUTHORIZED_CLUB_ACCESS);
+ }🤖 Prompt for AI Agents
In `@backend/src/main/java/moadong/club/service/PromotionArticleService.java`
around lines 33 - 36, The createPromotionArticle method currently only looks up
Club by ID; add an authorization check to ensure the authenticated user is a
member of that Club before creating the article: after converting
request.clubId() and loading Club via clubRepository.findClubById(...) (as
shown) obtain the current user's id (via your existing auth helper/service or
SecurityContext), then verify membership (e.g., check Club.getMembers() or call
membershipRepository.existsByClubIdAndUserId(...)); if the check fails throw a
RestApiException with an appropriate ErrorCode (e.g., ErrorCode.FORBIDDEN or a
new CLUB_MEMBERSHIP_REQUIRED) to block creation. Ensure you reference
createPromotionArticle, PromotionArticleCreateRequest, clubRepository, Club and
RestApiException when implementing.
There was a problem hiding this comment.
@lepitaaar, 네, 이해했습니다! 동아리 멤버십 검증 로직을 추가하시면 보안이 강화될 것 같습니다. 👍
🧠 Learnings used
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
#️⃣연관된 이슈
#1122
📝작업 내용
홍보게시판 API 엔드포인트 구현
추가된 엔드포인트
/api/promotion/api/promotion구조
promotion_articles컬렉션)Instant사용 (eventStartDate,eventEndDate)Request/Response 예시
GET Response:
{ "articles": [ { "clubName": "WAP", "clubId": "1230badsf", "title": "WAP 최종전시..", "location": "향파관", "eventStartDate": "2025-12-01T00:00:00Z", "eventEndDate": "2025-12-02T00:00:00Z", "description": "홍보 설명", "images": ["https://cdn.moadong.com","https://cdn.moadong.com~~~"] } ] }POST Request:
{ "clubId": "1230badsf", "title": "WAP 최종전시..", "location": "향파관", "eventStartDate": "2025-12-01T00:00:00Z", "eventEndDate": "2025-12-02T00:00:00Z", "description": "홍보 설명", "images": ["https://cdn.moadong.com","https://cdn.moadong.com~~~"] }Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.