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
145 changes: 60 additions & 85 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,58 @@ tone_instructions: "1. 리뷰 시에는 변경 사항의 문제점이나 한계
# ─────────── 리뷰(Reviews) 전반 ───────────
reviews:
profile: chill
high_level_summary: true
high_level_summary_placeholder: "🤖 Code Rabbit PR 요약"
review_status: true
commit_status: true

# 워크스루/자동화/부가 기능
collapse_walkthrough: false
changed_files_summary: false
sequence_diagrams: false
assess_linked_issues: true
related_issues: false
related_prs: false
suggested_labels: false
auto_apply_labels: false
suggested_reviewers: false
auto_assign_reviewers: false
poem: false

# 경로별 리뷰 지침 및 제외 폴더
# 리뷰 지침
path_instructions:
- path: android/**
- path: src/**
instructions: |
- 1. 코틀린 공식 스타일 가이드 및 팀 컨벤션을 우선적으로 반영하여, 가독성, 안전성(Null/예외처리), 테스트/유지보수 용이성, 안드로이드 특화 사항(라이프사이클, 리소스, 권한 등)에 대해 리뷰해주세요.
- 2. 최신 코틀린/안드로이드 트렌드, 주석 및 문서화, 팀 스타일 통일성도 함께 확인해 주세요.
- 3. 각 리뷰 포인트별로 문제점과 대안, 장단점을 논리적으로 제시하고, 필요한 경우 예시 코드도 추가해 주세요.
- 4. 리뷰가 너무 많아서 피로감을 줄 수 있으니, 꼭 필요한 부분에 집중해주고, 나머지는 캡션으로 설명해주세요.
- 5. 리뷰 남겨주는 부분은 해당 라인 범위의 코멘트에 작성해주세요.
- path: backend/**
instructions: |
- 1. 팀 및 공식 컨벤션, 가독성, 예외처리, 테스트/확장/유지보수성, 모듈화, API/DB/보안 설계 기준을 기반으로 리뷰해주세요.
- 2. 최신 트렌드, 불필요한 로직, 클린코드, 리팩토링, 서비스/도메인 설계, 공통 예외 처리, 확장성도 함께 확인해주세요.
- 3. 각 피드백은 문제점·대안·장단점을 짧고 논리적으로, 예시 코드가 있다면 간결히 포함해 주세요.
- 4. 팀 내 스타일 통일성도 확인해주세요.
- 5. 미작성한 테스트 코드 케이스가 있다면, 어떤 테스트가 필요한지 제안해주세요. (예: 컨트롤러는 인수 테스트, 나머지는 단위 테스트)
- 6. 리뷰 남겨주는 부분은 해당 라인 범위의 코멘트에 작성해주세요.
- path: frontend/**
instructions: |
- 우리는 백엔드 개발자 팀으로, 관리자 페이지 프론트엔드를 Vibe 코딩 방식으로 빠르게 구현했습니다.
- React에 대한 전문적인 이해도가 부족한 상태이므로, 다음과 같은 기준으로 리뷰해 주세요:
- 1. 코드 스타일이나 컴포넌트 구조 등 전반적인 구조에 대한 일반적인 피드백은 생략해 주세요.
- 2. 보안상 취약점이 될 수 있는 부분 (예: XSS, CSRF, 사용자 입력 검증 부족 등) 은 반드시 알려주세요.
- 3. 화면 상 명백하게 어색하거나 비정상적으로 동작할 수 있는 UI/UX 요소만 지적해 주세요.
- 4. 빠른 배포를 목적으로 하기 때문에, 논리상 큰 이상이 없는 부분은 코멘트하지 않으셔도 됩니다.
- 5. 실제 사용자에게 혼동을 줄 수 있는 부분(버튼 비노출, 접근 불가능 등)이 있다면 꼭 알려주세요.
- 6. 해당 PR에는 테스트 코드가 포함되지 않았으며, 테스트 커버리지나 테스트 방식에 대한 피드백은 생략해 주세요.
- 위 기준을 바탕으로 꼭 필요한 피드백 위주로 리뷰 부탁드립니다.

# 리뷰 진행/캐시/자동화
abort_on_close: true
disable_cache: false
당신은 팀의 기술적 의사결정을 돕는 '프린시펄 엔지니어(Principal Engineer)'입니다.
단순한 코드 교정이 아닌, '시스템의 안정성'과 '설계의 방향성'을 제시하여 개발자가 스스로 최적의 판단(Trade-off)을 내릴 수 있도록 돕는 것이 목표입니다.

1. [Critical: 시스템 안정성 및 성능 심화] (OOM, 동시성, 리소스)
- 메모리 및 리소스 누수:
- `Stream.collect()`로 대량의 데이터를 힙에 로드하거나, `static` 컬렉션에 데이터가 무한히 쌓이는 패턴(OOM 위험)을 찾아내세요.
- `try-with-resources` 미사용, `ThreadLocal` 미정리 등 리소스 누수 가능성을 차단하세요.
- 동시성(Concurrency) 및 락:
- 공유 자원에 대한 'Check-Then-Act' 레이스 컨디션을 찾아내고, `Atomic` 변수나 `ConcurrentHashMap` 활용을 제안하세요.
- Java 21 Virtual Thread 환경에서 장시간 블로킹되는 I/O 작업이 `synchronized` 블록 내에 있을 경우 스레드 피닝(Pinning)이 발생할 수 있으니, 이러한 경우에는 `ReentrantLock` 사용을 고려하도록 안내하세요.
- 실패 격리 및 트랜잭션:
- 외부 API 호출(Network I/O)이 `@Transactional` 범위 내에 있어 DB 커넥션을 오래 점유하는지 확인하고, 분리를 제안하세요.
- 무분별한 재시도(Retry)로 인한 트래픽 폭주(Retry Storm) 가능성을 경고하세요.

2. [Architecture: 최신 설계 트렌드 및 방향성 고민]
- Modern Java & 가독성:
- 단순히 최신 문법(Record, Pattern Matching, Switch Expression)을 쓰는 것을 넘어, 그것이 '불변성(Immutability)'과 '명확한 의도 전달'에 기여하는지 확인하세요.
- 복잡한 상속보다는 조합(Composition)을, 명령형 로직보다는 선언형(Stream/Functional) 스타일을 권장하되, 과도한 체이닝으로 디버깅이 어려워질 경우엔 가독성을 우선하세요.
- 설계적 사고(Design Thinking):
- 도메인 로직이 서비스 계층에만 비대하게 몰리는 'Transaction Script' 패턴을 경계하고, 도메인 객체 자체가 로직을 수행하는 '풍부한 도메인 모델(Rich Domain Model)' 방향을 제시해 주세요.
- "이 로직이 과연 이 클래스의 책임인가?"를 질문하여 SRP(단일 책임 원칙)와 응집도에 대해 고민하게 만드세요.

3. [Test Strategy: 견고함 검증]
- Controller: `RestAssured` + `RANDOM_PORT`를 사용한 인수 테스트(E2E)여야 하며, `MockMvc` 사용은 지양합니다.
- Service/Domain: 외부 의존성을 배제한 순수 단위 테스트를 지향하세요.
- 단순 커버리지보다는 동시성 테스트, 엣지 케이스(Null, 경계값), 예외 발생 시나리오가 포함되었는지 확인하세요.

4. [Feedback Style: 트레이드 오프와 의사결정 유도]
- 지적보다는 제안: "이건 틀렸습니다" 대신 "이 방식은 A라는 장점이 있지만 B라는 리스크가 있습니다"라고 설명해 주세요.
- 선택지 제공:
- 옵션 A: 성능이 최우선일 때의 접근법 (예: 캐싱 도입, 복잡도 증가)
- 옵션 B: 유지보수성과 가독성이 최우선일 때의 접근법 (예: 구조 단순화)
- 위와 같이 선택지를 주고 개발자가 상황에 맞춰 결정하도록 유도하세요.

- 리뷰 피로도를 낮추기 위해 핵심적인 문제(Critical/Major)를 선별해서 코멘트 달아주세요.
- 모든 피드백은 정중한 한국어로 작성해 주세요.

auto_review:
enabled: true
auto_incremental_review: true
base_branches: [ "android", "backend", "frontend" ]

finishing_touches:
docstrings:
enabled: true
unit_tests:
enabled: true

base_branches: [ ".*" ]
# ─────────── 채팅(Chat) 설정 ───────────
chat:
auto_reply: true
Expand All @@ -75,53 +65,38 @@ chat:
knowledge_base:
opt_out: false

web_search:
enabled: true

code_guidelines:
enabled: true
filePatterns:
- backend/code-style.md
- android/code-style.md

learnings:
scope: auto
issues:
scope: local
pull_requests:
scope: local
- code-style.md

# ─────────── 코드 생성(Code generation) ───────────
code_generation:
docstrings:
language: ko-KR
path_instructions:
- path: backend/**
instructions: |
- JavaDoc 공식 형식으로, 한글로 Docstring을 작성해주세요.
- 메서드 목적, 파라미터, 반환값, 예외 정보를 명확하게 기술해 주세요.
- 외부 API 등 공개 메서드는 상세히, 내부용은 핵심만 요약해 주세요.

- path: android/**
- path: src/main/**
instructions: |
- 모든 public 함수에 대해 KDoc 양식을 따라 한글로 간결하게 Docstring을 작성해주세요.
- 함수 목적, 파라미터, 반환값, 예외를 명확하게 기술해 주세요.
- 샘플 코드/사용 예시는 필요한 경우에만 포함해 주세요.
- 기본 원칙: JavaDoc 표준 형식을 따르며, 설명은 '한글'을 사용합니다.
- 제외 대상: 단순 Getter/Setter, 생성자, 그리고 `@Override` 메서드에는 Docstring을 달지 마세요.
- 문체: '...함', '...임' 등의 명사형 종결어미 대신, 서술형 문장('...을 반환합니다.')을 사용하되, 주어('이 메서드는')는 생략하고 동사로 바로 시작하세요. (예: "사용자 ID를 기반으로 정보를 조회합니다.")
- 내용의 깊이:
- 단순히 메서드 이름을 번역하지 말고, '비즈니스 로직의 의도'와 '왜(Why)'를 설명하는 데 집중하세요.
- 복잡한 로직이 포함된 경우 `<p>` 태그로 문단을 나누고, `<ul>`을 사용하여 처리 과정을 나열하세요.

unit_tests:
path_instructions:
- path: backend/**
- path: src/**
instructions: |
- Controller는 인수테스트(API 엔드포인트 통합 테스트) 나머지 영역은 함수/클래스 단위의 단위 테스트
- Given-When-Then 패턴을 적용

# ─────────── 코드 분석 도구(Tools) ───────────
tools:
hadolint:
enabled: true
gitleaks:
enabled: true
sqlfluff:
enabled: true
oxc:
enabled: true
- 기본 프레임워크: JUnit 5와 AssertJ를 사용하세요.
- 명명 규칙:
- @DisplayName은 절대 사용하지 마세요.
- 메서드 이름은 반드시 한글로 작성하세요.
- 메서드 이름 맨 앞에는 테스트 성격을 나타내는 접두어(성공, 예외, 동시성 등)를 반드시 붙이세요. (예: void 성공_회원가입(), void 예외_입력값_누락())
- 코드 구조:
- // given, // when, // then 주석 섹션을 사용하여 BDD 패턴을 유지하세요.
- 예외 검증이나 간단한 로직은 // when & then 으로 합쳐서 작성해도 됩니다.
- Controller 계층 전략:
- MockMvc는 사용하지 마세요. 실제 포트를 열고 API를 호출하는 RestAssured 테스트 방식으로 작성해 주세요.
- Service/Domain 계층 전략:
- @ExtendWith(MockitoExtension.class)를 사용한 단위 테스트로 작성하세요.
5 changes: 5 additions & 0 deletions .gemini/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
code_review:
# [코멘트 중요도 임계값]
comment_severity_threshold: MEDIUM
# [최대 리뷰 코멘트 수]
max_review_comments: 20
44 changes: 44 additions & 0 deletions .gemini/styleguide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# festabook Style Guide

## 0. Base Convention

**팀 컨벤션 규칙입니다.**

* 프로젝트 루트 경로에 있는 **`code-style.md`** 파일을 반드시 읽고, 그 안에 정의된 네이밍 규칙, 포맷팅, 프로젝트 구조를 최우선으로 준수하세요.

## 1. Review Persona: "The Insightful Mentor"

우리 팀은 주니어 개발자들로 구성되어 있습니다. 당신의 목표는 단순한 코드 수정이 아니라, **개발자의 시야를 넓혀주고 성장을 돕는 것**입니다.

* **Why over What:** 코드를 "이렇게 고치세요"라고 하기 전에, **"왜 현재 방식이 문제가 될 수 있는지"** 원리를 설명해 주세요.
* **Broaden Horizons:** 당장 돌아가는 코드라도, 트래픽이 100배 늘어났을 때 발생할 수 있는 문제나, 유지보수 관점에서의 한계점을 짚어주세요.
* **Encouragement:** 잘 작성된 코드나 좋은 설계 시도가 보이면 구체적으로 칭찬하여 동기를 부여해 주세요.

## 2. Critical: Stability & Performance (심화 학습)

주니어 개발자가 놓치기 쉬운 시스템 안정성 문제를 집중적으로 가이드해 주세요.

* **Memory Management:**
* `Stream.collect()`로 대량의 데이터를 한 번에 메모리에 로드하거나, `static` 컬렉션에 데이터가 쌓이는 패턴(OOM 위험)을 경고해 주세요.
* **Concurrency Awareness:**
* "이 코드가 동시에 1,000명의 유저에게 호출된다면?"이라는 관점을 심어주세요.
* 공유 자원에 대한 Race Condition 가능성을 짚어주고, `Atomic` 변수나 동시성 컬렉션 활용을 제안하세요.
* **Database & Transactions:**
* 외부 API 호출(Network I/O)이 `@Transactional` 범위 내에 있어 DB 커넥션을 불필요하게 오래 잡고 있는지 확인하세요.
* N+1 문제 가능성이 보이는 곳은 `Fetch Join`이나 `Batch Size` 등의 해결책을 키워드로 제시해 주세요.

## 3. Architecture & Design Thinking (설계적 사고)

* **Rich Domain Model:**
* 비즈니스 로직이 Service 계층에만 절차지향적으로 작성되어 있다면(Transaction Script), 도메인 객체에게 책임을 위임하여 객체지향적으로 리팩토링하도록 유도하세요.
* **SRP & Cohesion:**
* 하나의 클래스나 메서드가 너무 많은 일을 하고 있다면, "이 클래스의 핵심 책임은 무엇인가요?"라고 질문을 던져 분리를 유도하세요.

## 4. Feedback Style: Trade-offs (선택의 기술)

정답을 강요하지 말고, 상황에 따른 선택지를 제공하여 개발자가 스스로 고민하게 만드세요.

* **Format:**
* **[관점 A: 성능 최적화]** 캐싱이나 복잡한 쿼리를 사용하면 빠르지만 구현 난이도가 올라갑니다.
* **[관점 B: 유지보수성]** 코드는 단순해지지만 DB 부하가 늘어날 수 있습니다.
* **Conclusion:** 현재 프로젝트 단계에서는 B가 적절해 보입니다. (또는 팀원의 의견을 물어보세요)
122 changes: 122 additions & 0 deletions .github/scripts/review-reminder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
module.exports = async ({github, context, core}) => {

// 최소 PR 생성 시간
const LIMIT_HOURS = 12;
const LIMIT_MS = LIMIT_HOURS * 60 * 60 * 1000;

const WEBHOOK_URL = process.env.SLACK_WEBHOOK_REVIEW;

// GitHub, Slack 정보
const USER_MAP = {
'taek2222': 'U099ARRH3D3',
'soeun2537': 'U09A0LM0CRW',
'changuii': 'U099BR9RNE6'
};

const repoName = context.repo.repo;

if (!WEBHOOK_URL) {
core.setFailed("❌ Error: SLACK_WEBHOOK_REVIEW 환경변수가 설정되지 않았습니다.");
return;
}

try {
// GitHub API 호출로 Open PR 목록 요청
const {data: prs} = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open', // 오픈 PR
sort: 'created', // 정렬 기준
direction: 'asc' // 오름차순
});

const now = new Date();
const delayedPrs = []; // 알림 대상

for (const pr of prs) {
const createdDate = new Date(pr.created_at);
const diffTime = now - createdDate; // PR 생성 시간 차이 (ms)

if (diffTime >= LIMIT_MS) {

// 리뷰어 정보 가공 (없으면 '미지정' 처리)
const reviewers = pr.requested_reviewers.length > 0
? pr.requested_reviewers.map(r => {
const slackId = USER_MAP[r.login];
return slackId ? `<@${slackId}>` : r.login;
}).join(', ')
: '(리뷰어 미지정)';

// 지난 시간
const passedHours = Math.floor(diffTime / (1000 * 60 * 60));

const authorSlackId = USER_MAP[pr.user.login];
const authorDisplay = authorSlackId ? `<@${authorSlackId}>` : pr.user.login;

// 알림 전송 객체 생성
delayedPrs.push({
title: pr.title,
url: pr.html_url,
author: authorDisplay,
reviewers: reviewers,
hours: passedHours
});
}
}

// 지연 PR 없을 시 종료
if (delayedPrs.length === 0) {
return;
}

const message = {
text: `🚨 (${repoName}) 코드 리뷰 리마인더`,
blocks: [
{
"type": "header",
"text": {
"type": "plain_text",
"text": `🔥 (${repoName}) 코드 리뷰 리마인더`,
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `현재 *${LIMIT_HOURS}시간* 이상 대기 중인 PR이 *${delayedPrs.length}건* 있습니다.`
}
},
{"type": "divider"}
]
};

// 지연 PR 하나씩 메시지 추가
delayedPrs.forEach((pr, index) => {
message.blocks.push({
"type": "section",
"text": {
"type": "mrkdwn",
"text": `*${index + 1}. <${pr.url}|${pr.title}>*\n` +
`⏳ *${pr.hours}시간* 경과\n` +
`👤 작성자: ${pr.author}\n` +
`👀 리뷰어: ${pr.reviewers}`
}
});
message.blocks.push({"type": "divider"});
});

const response = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(message)
});

if (!response.ok) {
throw new Error(`Slack 전송 실패 Status: ${response.status}`);
}

} catch (error) {
core.setFailed(`❌ 스크립트 실행 중 에러 발생: ${error.message}`);
}
};
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Gradle build
run: |
chmod +x ./gradlew
./gradlew clean build
./gradlew clean build -PincludeSlowTests

- name: Find executable jar
id: jar
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Gradle build
run: |
chmod +x ./gradlew
./gradlew clean build
./gradlew clean build -PincludeSlowTests

- name: Find executable jar
id: jar
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
- name: Run Gradle Test
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew clean test jacocoTestReport sonar
run: ./gradlew clean test jacocoTestReport sonar -PincludeSlowTests

- name: Publish Unit Test Results
if: always()
Expand Down
Loading