-
Notifications
You must be signed in to change notification settings - Fork 3
[feat] 홍보게시판 API 구현 #1125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop/be
Are you sure you want to change the base?
[feat] 홍보게시판 API 구현 #1125
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인증된 사용자가 해당 동아리의 멤버인지 확인이 필요합니다.
현재 구현에서는 인증된 사용자라면 어떤 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 바꿀꺼임
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.