Skip to content

feat: 코스발견 탭 상단 배너 조회 API 추가 - #209

Merged
unam98 merged 2 commits into
devfrom
feat/banner-api
Aug 3, 2026
Merged

feat: 코스발견 탭 상단 배너 조회 API 추가#209
unam98 merged 2 commits into
devfrom
feat/banner-api

Conversation

@unam98

@unam98 unam98 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

배경

코스발견 탭 상단 배너 이미지가 현재 파이어베이스에서 직접 내려오고 있음. 배너를 서버에서 관리할 수 있도록 조회 API를 우선 추가함. Android 연동은 후속 작업.

변경 사항

  • Banner 엔티티 (imageUrl, linkUrl, sortOrder, isActive)
  • BannerRepository.findByIsActiveTrueOrderBySortOrderAscIdAsc() — sortOrder 동률 시 id를 보조 정렬키로 사용해 결정론적으로 정렬
  • GET /api/banner — 활성 배너를 sortOrder(동률 시 id) 순으로 반환. 응답 형태: { data: { banners: [{ index, imageUrl, linkUrl }] } } (배너 없음 콜백 없이 banners: []로 응답)
  • SuccessStatus.GET_BANNER_SUCCESS 추가

영향 범위

신규 도메인 추가로 기존 API/로직에는 영향 없음. 인증 불필요한 공개 엔드포인트.

검증

  • ./gradlew compileJava 성공
  • 로컬 docker-compose(postgres/redis)로 서버 기동 후 GET /api/banner 호출 → ddl-auto: updatebanner 테이블 자동 생성 확인, 200 {"data":{"banners":[]}} 정상 응답 확인 (배너 데이터가 아직 없어 빈 배열)

Summary by CodeRabbit

  • New Features
    • Added an API endpoint for retrieving active banners.
    • Banners are returned with their image links, destination links, and display order.
    • Results are sorted by banner order and include a successful response status.

Banner 엔티티/Repository/Service/Controller 추가, GET /api/banner로 활성 배너를 sortOrder 순으로 반환. Android 연동은 후속 작업.
@unam98 unam98 self-assigned this Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added banner persistence, response DTOs, service mapping, and a /api/banner GET endpoint for active banners.

Changes

Banner retrieval

Layer / File(s) Summary
Banner persistence contract
src/main/java/org/runnect/server/banner/entity/Banner.java, src/main/java/org/runnect/server/banner/repository/BannerRepository.java
Added the Banner entity and a repository query for active banners ordered by sortOrder.
Banner response mapping
src/main/java/org/runnect/server/banner/dto/response/BannerResponse.java, src/main/java/org/runnect/server/banner/dto/response/GetBannerResponseDto.java, src/main/java/org/runnect/server/banner/service/BannerService.java
Added response DTOs. The service maps ordered banners to zero-based indexed responses.
Banner API endpoint
src/main/java/org/runnect/server/common/constant/SuccessStatus.java, src/main/java/org/runnect/server/banner/controller/BannerController.java
Added the /api/banner GET endpoint with the GET_BANNER_SUCCESS HTTP 200 status.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant BannerController
  participant BannerService
  participant BannerRepository
  Client->>BannerController: GET /api/banner
  BannerController->>BannerService: getBanners()
  BannerService->>BannerRepository: query active banners by sort order
  BannerRepository-->>BannerService: ordered banners
  BannerService-->>BannerController: GetBannerResponseDto
  BannerController-->>Client: HTTP 200 ApiResponseDto
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the addition of the banner retrieval API for the 코스발견 tab, which matches the main changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/banner-api

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/org/runnect/server/banner/dto/response/GetBannerResponseDto.java`:
- Around line 13-17: Update BannerService to return List<BannerResponse>
directly and pass that list as the ApiResponseDto data payload, matching the
declared array response shape. Remove GetBannerResponseDto and its factory usage
so responses are not wrapped under a banners property.

In `@src/main/java/org/runnect/server/banner/repository/BannerRepository.java`:
- Line 10: Update BannerRepository.findByIsActiveTrueOrderBySortOrderAsc to
order active banners by sortOrder ascending and a stable secondary key such as
id ascending, then rename the repository method accordingly and update
BannerService to call the renamed method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e0077fc-6715-42af-b52a-fca3554e7ac8

📥 Commits

Reviewing files that changed from the base of the PR and between 80faa2d and 5161fdd.

📒 Files selected for processing (7)
  • src/main/java/org/runnect/server/banner/controller/BannerController.java
  • src/main/java/org/runnect/server/banner/dto/response/BannerResponse.java
  • src/main/java/org/runnect/server/banner/dto/response/GetBannerResponseDto.java
  • src/main/java/org/runnect/server/banner/entity/Banner.java
  • src/main/java/org/runnect/server/banner/repository/BannerRepository.java
  • src/main/java/org/runnect/server/banner/service/BannerService.java
  • src/main/java/org/runnect/server/common/constant/SuccessStatus.java

Comment thread src/main/java/org/runnect/server/banner/repository/BannerRepository.java Outdated

@unam98 unam98 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

안드로이드 개발자 관점에서 백엔드(Spring/JPA) 신규 개념 위주로 코멘트 남겼습니다. 승인/거부와 무관한 학습용 코멘트예요.

import org.runnect.server.common.entity.AuditingTimeEntity;

@Getter
@Entity

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@entity — ORM 매핑
JPA(자바 ORM 표준)에게 "이 클래스는 DB 테이블 하나에 대응된다"고 알려주는 애너테이션. Room의 @Entity와 개념은 같은데, JPA는 필드 변경 감지(dirty checking)까지 자동화되어 있어서 트랜잭션 안에서 필드만 바꿔도(save() 명시 호출 없이) 커밋 시점에 알아서 UPDATE 쿼리가 나감.


@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

JPA는 왜 파라미터 없는 생성자가 필요할까
Hibernate가 DB row를 객체로 바꿀 때 리플렉션으로 빈 객체부터 만들고 필드를 채워 넣기 때문에 기본 생성자가 필수. 그렇다고 아무데서나 new Banner()로 빈 값 객체를 만들 수 있게 열어두긴 싫어서 PROTECTED로 제한 — JPA(같은 패키지/상속 구조)는 쓸 수 있지만 비즈니스 코드에서는 못 씀. 불변성을 지키기 위한 관용구.

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Banner extends AuditingTimeEntity {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AuditingTimeEntity — 공통 필드는 상속으로 분리
createdAt/updatedAt/deletedAt처럼 거의 모든 테이블에 필요한 필드를 @MappedSuperclass 부모 클래스로 빼서 상속받음. Spring Data JPA Auditing(@EnableJpaAuditing)이 저장/수정 시점에 자동으로 채워줘서, 매번 LocalDateTime.now()를 직접 넣을 필요가 없음.

public class Banner extends AuditingTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PK 생성 전략(GenerationType.IDENTITY)
DB의 auto-increment(PostgreSQL SERIAL)에 ID 생성을 위임하는 전략. INSERT 쿼리를 실제로 날려야 ID 값을 알 수 있다는 특징이 있음(SEQUENCE 전략과 달리 배치 insert 최적화가 제한적). 이 프로젝트는 기존 Course 엔티티와 동일하게 IDENTITY로 통일해서 씀.

@Column(nullable = false)
private Boolean isActive;

@Builder

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

빌더 패턴 — 생성자 대신 쓰는 이유
필드가 여러 개일 때 new Banner(a, b, c)처럼 순서/타입을 외워야 하는 걸 Banner.builder().imageUrl(...).linkUrl(...).build()처럼 이름을 붙여 조립하게 해줌(Lombok이 보일러플레이트 자동 생성). isActive처럼 항상 고정값(true)으로 시작해야 하는 필드는 빌더 파라미터에서 빼고 생성자 본문에서 직접 세팅.

import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@RestController = @Controller + @ResponseBody
메서드 리턴값을 뷰(HTML 템플릿)로 렌더링하는 대신, 그대로 JSON으로 직렬화해서 응답 바디에 실어줌. Android 쪽에서 쓰는 Retrofit 인터페이스의 반대편 — 서버가 이 JSON을 만들어서 내려주는 쪽이라고 보면 됨.


@RestController
@RequiredArgsConstructor
@RequestMapping("/api/banner")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

라우팅 — @RequestMapping + @GetMapping 조합
클래스 레벨 @RequestMapping으로 공통 prefix(/api/banner)를 잡고, 메서드별로 @GetMapping/@PostMapping 등으로 세부 경로+HTTP 메서드를 정의. 여기선 메서드에 경로를 안 붙였으니 최종 엔드포인트는 GET /api/banner.

private final BannerService bannerService;

@GetMapping
@ResponseStatus(HttpStatus.OK)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@ResponseStatus — 명시적 상태 코드
@RestController의 기본 성공 상태가 200이라 사실 이 줄이 없어도 동작은 같음. 이 프로젝트는 관례적으로 모든 엔드포인트에 상태 코드를 명시해둠(POST는 201 CREATED처럼 기본값과 다른 경우가 많아서, 아예 전부 명시하는 쪽으로 통일한 걸로 보임).

@GetMapping
@ResponseStatus(HttpStatus.OK)
public ApiResponseDto<GetBannerResponseDto> getBanners() {
return ApiResponseDto.success(SuccessStatus.GET_BANNER_SUCCESS, bannerService.getBanners());

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

공통 응답 포맷(response envelope)
모든 API가 {status, success, message, data} 구조로 통일돼서 내려감. 클라이언트가 API마다 다른 응답 구조를 파싱할 필요 없이 data 안쪽만 API별로 다르게 보면 되게끔 하는 컨벤션. SuccessStatus enum이 상태코드+메시지를 한 쌍으로 관리해서 문자열이 코드 여기저기 흩어지는 걸(매직 스트링) 막아줌.


GET_HEALTH_DATA_SUCCESS(HttpStatus.OK, "건강 데이터 조회 성공"),
GET_HEALTH_SUMMARY_SUCCESS(HttpStatus.OK, "건강 통계 조회 성공"),
GET_BANNER_SUCCESS(HttpStatus.OK, "배너 조회 성공"),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Enum으로 성공 상태를 관리하는 이유
API마다 성공 메시지를 하드코딩하면 오타나 중복이 나기 쉬움. SuccessStatus(HttpStatus, message) 쌍으로 몰아두면 이 서버의 모든 성공 응답 목록을 한 곳에서 파악할 수 있음. 에러도 동일한 패턴으로 ErrorStatus enum이 따로 있음.

sortOrder에 유니크 제약이 없어 값이 같은 배너가 있으면 정렬 순서가 요청마다 달라질 수 있어, id ASC를 보조 키로 붙여 결정론적으로 정렬되도록 함.
@unam98
unam98 merged commit e7a8c17 into dev Aug 3, 2026
2 checks passed
@unam98
unam98 deleted the feat/banner-api branch August 3, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants