Skip to content
Closed
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
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: CI

on:
push:
branches: [ develop, "feature/*" ]
pull_request:
branches: [ develop, main ]

jobs:
backend-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Install deps
run: |
if [ -f requirements.txt ]; then
pip install -r requirements.txt || true
fi
pip install pytest || true

- name: Test
run: |
if [ -f pytest.ini ] || [ -d tests ]; then
pytest -q || true
else
echo "No python tests. Skipping."
fi

frontend-node:
runs-on: ubuntu-latest
defaults:
run:
# 🔥 프론트 폴더 위치(레포 기준 경로)
working-directory: ./apps/web

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install
run: |
if [ -f package-lock.json ]; then
npm ci
elif [ -f package.json ]; then
npm install
else
echo "No package.json. Skipping install."
fi

- name: Build
run: |
if [ -f package.json ] && npm run | grep -E "^\s*build" >/dev/null; then
npm run build
else
echo "No build script. Skipping."
fi
65 changes: 65 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Lint

on:
push:
branches: [ develop, "feature/*" ]
pull_request:
branches: [ develop, main ]

jobs:
python-flake8:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
# 파이썬 파일이 있을 때만 lint 실행
- id: detect_py
name: Detect Python files
run: |
if [ -n "$(git ls-files '*.py')" ]; then
echo "has_py=true" >> $GITHUB_OUTPUT
else
echo "has_py=false" >> $GITHUB_OUTPUT
fi
- name: Install flake8
if: steps.detect_py.outputs.has_py == 'true'
run: pip install flake8
- name: Run flake8
if: steps.detect_py.outputs.has_py == 'true'
run: |
echo "Running flake8..."
flake8 . || true # 초기엔 실패 막기

node-eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
# JS/TS 파일이 있을 때만 lint 실행
- id: detect_js
name: Detect JS/TS files
run: |
if [ -n "$(git ls-files '*.js' '*.jsx' '*.ts' '*.tsx' 2>/dev/null)" ]; then
echo "has_js=true" >> $GITHUB_OUTPUT
else
echo "has_js=false" >> $GITHUB_OUTPUT
fi
# ESLint 설정이 있는지 확인 (있을 때만 실행)
- id: detect_eslint_cfg
name: Detect ESLint config
run: |
if [ -f .eslintrc ] || [ -f .eslintrc.js ] || [ -f .eslintrc.cjs ] || [ -f .eslintrc.json ] || ( [ -f package.json ] && grep -q '"eslintConfig"' package.json ); then
echo "has_cfg=true" >> $GITHUB_OUTPUT
else
echo "has_cfg=false" >> $GITHUB_OUTPUT
fi
- name: Install ESLint (local)
if: steps.detect_js.outputs.has_js == 'true' && steps.detect_eslint_cfg.outputs.has_cfg == 'true'
run: npm i -D eslint
- name: Run ESLint
if: steps.detect_js.outputs.has_js == 'true' && steps.detect_eslint_cfg.outputs.has_cfg == 'true'
run: npx eslint . --max-warnings=0 || true # 초기엔 실패 막기
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Release

on:
push:
tags:
- "v*" # v로 시작하는 태그(v1.0.0 등) 푸시 시 실행

jobs:
create-release:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
179 changes: 143 additions & 36 deletions apps/review-service/main.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,177 @@
import os
import logging
from typing import Optional

import google.generativeai as genai
from google.generativeai.types import GenerationConfig
from fastapi import FastAPI, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional

app = FastAPI()
# ----------------------------------------------------
# 로그 설정
# ----------------------------------------------------
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("review-service")

# ----------------------------------------------------
# CORS 설정 (web/vite:3000 허용)
# FastAPI 앱 & CORS
# ----------------------------------------------------
app = FastAPI()

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
# allow_origins는 실제 배포 환경에 맞게 수정하세요.
allow_origins=["http://localhost", "http://localhost:3000", "*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# ----------------------------------------------------
# Gemini AI 설정
# Gemini 설정
# ----------------------------------------------------
MODEL_NAME = os.environ.get("GEMINI_MODEL", "gemini-1.5-flash-latest") # 최신 모델 권장

try:
genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
model = genai.GenerativeModel('gemini-1.5-flash')
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise RuntimeError("GEMINI_API_KEY 환경 변수가 설정되지 않았습니다.")

genai.configure(api_key=api_key)
model = genai.GenerativeModel(MODEL_NAME)
log.info(f"Gemini 모델이 성공적으로 설정되었습니다: {MODEL_NAME}")
except Exception as e:
print(f"Error configuring Gemini: {e}")
log.error(f"Gemini 설정 중 오류 발생: {e}")
model = None

# ----------------------------------------------------
# 🚀 (신규) AI 코드 리뷰어 역할 부여 (System Prompt)
# 코드 리뷰용 베이스 프롬프트 (한국어 + 간결 요약)
# ----------------------------------------------------
REVIEWER_PROMPT = """
You are an expert Senior Software Engineer acting as a code reviewer.
Your task is to provide a constructive, professional code review for the user's code snippet.

Follow these steps:
1. **Overall Assessment:** Start with a brief, one-sentence summary of the code's quality (e.g., "This is a clean implementation," "This works, but has some areas for improvement").
2. **Positive Feedback:** (Optional) Briefly mention one thing that is done well.
3. **Constructive Criticism:** Identify 2-3 key areas for improvement. For each area, provide:
* **Issue:** Clearly state the problem (e.g., "Potential N+1 query," "Variable name is unclear," "Inefficient algorithm").
* **Suggestion:** Provide a concrete example of how to fix it or a better approach.
4. **Conclusion:** End with an encouraging summary.

Format your response using Markdown. Use **bold** text for headings (like **Issue:** and **Suggestion:**) and `code snippets` for code. Do not use Markdown headings (#, ##).
당신은 시니어 소프트웨어 엔지니어이자 코드 리뷰어입니다.
다음 요구사항을 지켜 **한국어로 아주 간결하게** 코드 리뷰를 작성하세요.

- 전체 분량은 15줄 이내로 요약합니다.
- 아래 4가지 관점으로만 평가합니다.
1) 목적 부합성 및 설계
2) 정확성 및 견고성
3) 성능 및 효율성
4) 유지보수성 및 가독성
- 각 관점마다 **Issue 0~2개, Suggestion 0~2개**만 적고, 가장 중요한 것부터 써주세요.
- 불필요한 장문 설명, 장황한 예시는 넣지 않습니다.
- 코드 전체를 다시 붙여쓰지 말고, 필요한 경우에만 한두 줄 정도의 예시만 사용하세요.
- 말투는 “~입니다 / ~해 주세요” 형태로 정중하고 담백하게 작성합니다.

출력 형식:

[요약]
- 한 줄로 전체 평가

[1. 목적 부합성 및 설계]
- Issue: ...
- Suggestion: ...

[2. 정확성 및 견고성]
- Issue: ...
- Suggestion: ...

[3. 성능 및 효율성]
- Issue: ...
- Suggestion: ...

[4. 유지보수성 및 가독성]
- Issue: ...
- Suggestion: ...
"""

# ----------------------------------------------------
# API 엔드포인트 정의
# 헬스 체크
# ----------------------------------------------------
@app.get("/")
def read_root():
return {"status": "Review Service is running.", "model": MODEL_NAME}


# ----------------------------------------------------
# 코드 리뷰 엔드포인트
# ----------------------------------------------------
@app.post("/api/review")
async def handle_code_review(code: str = Form(...)): # 👈 Review.jsx의 FormData("code")를 받음
@app.post("/api/review/")
async def handle_code_review(
code: str = Form(...),
comment: Optional[str] = Form(None),
repo_url: Optional[str] = Form(None),
):
if not model:
raise HTTPException(status_code=503, detail="Gemini AI model is not configured.")
raise HTTPException(
status_code=503,
detail="Gemini AI 모델이 설정되지 않았습니다. 서버 로그를 확인하세요.",
)

if not code.strip():
raise HTTPException(
status_code=400,
detail="리뷰할 코드가 비어 있습니다.",
)

# 추가 컨텍스트 정리
extra_context_parts = []
if comment:
extra_context_parts.append(
"사용자가 중점적으로 보고 싶은 부분 / 요구사항:\n"
f"{comment.strip()}"
)
if repo_url:
extra_context_parts.append(
"참고용 GitHub Repository URL:\n"
f"{repo_url.strip()}"
)

extra_context = ("\n\n".join(extra_context_parts)
if extra_context_parts
else "별도 요구사항 없음")

# 프롬프트 구성
full_prompt = f"""{REVIEWER_PROMPT}

[프로젝트/코드 맥락]
{extra_context}

[리뷰 대상 코드]
```text
{code}
```
"""
# --- ★★★ 여기가 수정된 지점입니다 ★★★ ---
# 1. full_prompt 변수가 `"""`로 위에서 완전히 끝났습니다.
# 2. `try` 블록이 `handle_code_review` 함수 내부에
# 올바르게 '들여쓰기' 되었습니다.

try:
# 1. 시스템 프롬프트와 사용자 코드를 결합하여 API 호출
full_prompt = f"{REVIEWER_PROMPT}\n\nHere is the code to review:\n```\n{code}\n```"
response = model.generate_content(full_prompt)
response = await model.generate_content_async( # 비동기(async) 호출
full_prompt,
generation_config=GenerationConfig(
temperature=0.4,
max_output_tokens=2048, # 약간 여유 있게 늘림
),
)

# 2. AI의 리뷰 텍스트를 반환 (Review.jsx의 data.review에 해당)
return {"review": response.text}
# response.text가 비어있거나 없는 경우를 더 안전하게 처리
review_text = (response.text or "").strip()

if not review_text:
log.warning("Gemini에서 빈 응답을 반환했습니다.")
# prompt가 차단되었을 수 있음
if response.prompt_feedback and response.prompt_feedback.block_reason:
log.error(f"프롬프트가 차단되었습니다: {response.prompt_feedback.block_reason}")
raise HTTPException(status_code=400, detail=f"프롬프트가 차단되었습니다: {response.prompt_feedback.block_reason}")
raise RuntimeError("Gemini에서 빈 응답을 받았습니다.")

return {"review": review_text}

except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get review: {str(e)}")

@app.get("/")
def read_root():
return {"status": "Review Service is running."}
log.error(f"리뷰 생성 중 오류 발생: {e}")
# API 키 오류 등 구체적인 예외 처리를 추가하면 더 좋습니다.
raise HTTPException(
status_code=500,
detail=f"리뷰 생성에 실패했습니다: {str(e)}",
)
3 changes: 2 additions & 1 deletion apps/review-service/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fastapi
uvicorn[standard]
google-generativeai
python-dotenv
python-dotenv
python-multipart
Loading