Post

[GitPulse #4] 토큰 기반 요청 & 주간 MVP 기능 구현하기

[GitPulse #4] 토큰 기반 요청 & 주간 MVP 기능 구현하기

✅ 오늘의 목표

  • GitHub API 요청 시 JWT 토큰을 포함한 인증 방식 일괄 변경
  • 사용자별 커밋 수 분석을 통해 이번 주 MVP 추출
  • PR 설명에 Markdown 렌더링 적용

🧩 문제 상황 / 배경

현재 팀 프로젝트에서 GitHub API를 활용해 조직, 레포지토리, 커밋 데이터를 가져오고 있었습니다.
그러나 클라이언트에서 직접 GitHub API를 호출하는 방식은 다음과 같은 문제를 일으켰습니다.

  • 비인증 요청이므로 접근할 수 있는 데이터에 한계가 있었고,
  • 모든 팀원이 같은 GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET를 쓰다 보니 API 호출 횟수가 금방 초과되는 문제가 발생했습니다.
  • 보안상으로도 클라이언트에서 민감한 정보를 직접 다루는 구조는 취약했습니다.

추가로

  • 팀원들의 기여도를 보다 직관적으로 파악할 수 있도록 커밋 수 기반 MVP 기능을 추가하고 싶었고,
  • GitHub의 PR 메시지는 Markdown 포맷으로 작성되지만 UI상 렌더링이 제대로 되지 않아 가독성이 떨어지는 문제가 있었습니다.

🛠️ 해결 과정 / 코드 정리

🔸 GitHub API 요청 구조 개선 (JWT 인증 & 프록시 서버)

기존에는 axios.get(BASE_URL) 형식으로 클라이언트에서 GitHub API를 직접 호출했지만, jwt 토큰을 활용한 프록시 서버 경유 요청으로 변경

✅ fetchWithToken 함수 - frontend/src/apis/github.js

1
2
3
4
5
6
7
8
9
export const fetchWithToken = async (path, params = {}) => {
  const token = localStorage.getItem("jwt");
  const res = await axios.get(`${API_BASE}/github/proxy`, {
    params: { path, ...params },
    headers: { Authorization: `Bearer ${token}` },
    withCredentials: true,
  });
  return res.data;
};
  1. 프론트에서 JWT 토큰을 포함한 요청을 프록시 서버에 보낸다.
  2. 프록시 서버가 GitHub API를 대신 호출해 데이터를 가져오고, 클라이언트에 전달한다.

❓ 프록시 서버란 ? 클라이언트와 서버 사이에서 중간 역할을 하는 서버

역할설명
1. 우회 요청서버에 직접 접근하지 않고 프록시를 거쳐서 요청
2. CORS 우회프론트엔드에서 API 호출 시 CORS 오류가 날 때, 프록시를 두고 우회함
3. 보안클라이언트 IP를 숨길 수 있음 (대신 프록시의 IP로 요청)
4. 캐싱 기능자주 요청되는 데이터를 저장해두고 빠르게 응답

🔁 기존 코드 → 변경 코드

기존 GitHub API 요청 방식:

1
2
3
4
5
6
7
8
export const getOrganizationsByUser = async (username) => {
  try {
    const res = await axios.get(`${BASE_URL}/users/${username}/orgs`);
    return res.data;
  } catch (error) {
    console.log("조직 가져오기 실패", error);
  }
};

변경 후:

1
2
3
4
5
6
7
8
export const getOrganizationsByUser = async (username) => {
  try {
    const res = await fetchWithToken(`/users/${username}/orgs`);
    return res;
  } catch (error) {
    console.log("조직 가져오기 실패", error);
  }
};

Organizations의 멤버 및 레포 정보도 마찬가지로 fetchWithToken으로 일괄 적용.


🔸 ReactMarkdown 적용 (PR 메시지 처리)

PR 메시지는 대부분 Markdown 포맷으로 작성되지만, 기존 UI에서는 일반 텍스트로 출력되어 가독성이 떨어짐

PR 메시지 예시

md 응답 데이터

1
"body": "## 📝 변경 사항\r\n\r\n> 커밋 단위로 명시적으로 짧게 작성합니다.\r\n\r\n1.\r\n\r\n## 🔍 변경 사항 세부 설명\r\n\r\n> 세부 설명을 작성합니다.\r\n\r\n-\r\n\r\n## 🕵️\u200d♀️ 요청사항\r\n\r\n> 팀원들에게 테스트나 리뷰를 요청합니다.\r\n\r\n-\r\n\r\n## 📷 스크린샷 (선택)\r\n\r\n> UI 변경이 있는 경우 스크린샷이나 GIF를 첨부합니다.\r\n\r\n## ✅ 작성자 체크리스트\r\n\r\n- [x] 커밋 메시지 컨벤션에 맞게 작성했습니다.\r\n- [x] 변경 사항에 대한 테스트를 했습니다(버그 수정/기능에 대한 테스트)\r\n- [ ] Self-review: commit 이나 오타 없이 코드가 스스로 검토됨\r\n- [ ] 로컬에서 모든 기능이 정상 작동함(버그수정 /기능에 대한 테스트)\r\n- [ ] 린터 및 포맷터로 코드 정리됨\r\n"

도입한 해결책: react-markdown 라이브러리

1
npm install react-markdown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import ReactMarkdown from "react-markdown";

<div className={orgs.RecentlyItem}>
    <h3>따끈따끈 PR 소식</h3>
    {pulls && (
    <div className={orgs.RecentPRItem}>
        <div>{pulls.title}</div>
        <div>{pulls.user.login}</div>
        <div>
            <ReactMarkdown>{pulls.body}</ReactMarkdown>
        </div>
        <div>{pulls.created_at}</div>
    </div>
    )}
</div> 

ReactMarkdown 도입 후 UI


🔸 이번 주 커밋 MVP 찾기

커밋 데이터 분석을 통해 현재 유저를 기준으로 일주일 간의 커밋 현황을 파악하고, 커밋 수가 가장 많은 유저를 이번 주 MVP로 선정했다.

일주일 날짜 배열 생성

1
2
3
4
5
const dateArray = [...Array(7)].map((_, i) => {
  const d = new Date(today);
  d.setUTCDate(today.getUTCDate() - (6 - i));
  return d.toISOString().split("T")[0];
});

커밋 수 집계 및 MVP 추출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const userCommitMap = {};
commit.forEach((commit) => {
  const dateStr = new Date(commit.commit.author.date).toISOString().split("T")[0];
  const authorLogin = commit.author?.login || commit.commit.author?.name || "anonymous";

  if (commitCountByDay[dateStr]) {
    commitCountByDay[dateStr].total += 1;
    if (authorLogin === curUserLogin) {
      commitCountByDay[dateStr].mine += 1;
    }
    userCommitMap[authorLogin] = (userCommitMap[authorLogin] || 0) + 1;
  }
});

const topCommitter = Object.entries(userCommitMap).reduce(
  (acc, [author, count]) => (count > acc.count ? { author, count } : acc),
  { author: "", count: 0 }
);

setTopCommit(topCommitter);
  1. 각 커밋의 작성자(author) 추출
    • commit.author?.login 또는 commit.commit.author.name을 활용해 커밋의 작성자를 식별합니다.
  2. 작성자별 커밋 수 누적 (userCommitMap 생성)
    • userCommitMap 객체에 작성자를 key로 등록하고, 커밋이 발견될 때마다 값을 1씩 증가시켜 누적합니다.
  3. MVP 선정 (topCommitter 계산)
    • userCommitMap을 순회하며 가장 많은 커밋 수를 기록한 작성자를 MVP로 선정합니다.
  4. 선정된 MVP 정보 저장
    • setTopCommit(topCommitter)를 통해 MVP 정보를 상태로 저장하고 UI에 반영합니다.

Object.entries() 란?

Object.entries(obj)는 객체의 모든 열거 가능한 속성(own enumerable properties)을 [key, value] 형태의 2차원 배열로 반환합니다.

1
Object.entries(obj)
  • obj: 객체
  • 반환값: [ [key1, value1], [key2, value2], ... ] 형태의 배열

다른 관련 메서드들

메서드설명반환값
Object.keys(obj)객체의 key 값들을 배열로 반환["name", "age", "job"]
Object.values(obj)객체의 value 값들을 배열로 반환["Alice", 25, "Developer"]
Object.entries(obj)객체를 [key, value] 배열로 반환[["name", "Alice"], ... ]

MVP 시각화 UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div className={orgs.RecentlyItem}>
    <h3>이번  MVP</h3>
    <div className={orgs.RankingItem}>
    {topCommit && (
        <>
        <div>{topCommit.author}</div>
        <img src="/img/topCommit.png" />
        <div>
            {topCommit.author}님의 이번주 커밋 수는
            <strong>{topCommit.count}</strong>번 입니        </div>
        </>
    )}
    </div>
</div>


✅ 결과 및 느낀 점

  • GitHub API 요청 시 JWT 토큰을 이용한 프록시 서버 방식으로 인증 문제와 보안 이슈를 해결
  • PR 설명을 Markdown으로 제대로 렌더링하면서, 가독성 있는 UI와 개발자 친화적인 환경을 구축
  • MVP 선정 기능을 통해 팀원 간의 커밋 활동을 게임화(gamification) 할 수 있었고, 실시간으로 기여도를 확인하는 재미도 생김.

🖼️ 작업 결과물

PR & MVP


Reference

이 포스트는 아래 게시글의 정보 및 이미지가 사용되었습니다.

  1. GitHub REST API v3 Documentation
  2. JWT (JSON Web Tokens) - Introduction
  3. ReactMarkdown GitHub
  4. MDN Web Docs - Date 객체

END

This post is licensed under CC BY 4.0 by the author.