diff --git a/README.md b/README.md
index ca8e8a4..ad80db4 100644
--- a/README.md
+++ b/README.md
@@ -1,132 +1,96 @@
+
+
# Dreamhack Readme Stats
-GitHub README 프로필에 표시할 수 있는 Dreamhack 워게임 통계 SVG 생성기입니다.
+**GitHub README에 Dreamhack 워게임 통계를 표시하세요**
-## 사용 방법
+[](https://github.com/search?q=%22dreamhack-readme-stats.vercel.app%2Fapi%2F%22+in%3Afile+filename%3AREADME.md&type=code)
+
+
-### Markdown
+
-```markdown
-
-```
+
-### HTML
+
-```html
-
-
-
-```
+
-실제 사용 시에는 `사용자명`을 여러분의 Dreamhack 사용자 이름으로 변경하세요.
+---
-## 예시
+## Quick Start
-다음은 실제 렌더링된 결과입니다:
+README에 아래 코드를 추가하고 `사용자명`을 본인의 Dreamhack 닉네임으로 변경하세요.
-
+### Wargame Stats
-마크다운 코드:
```markdown
-
-```
-
-HTML 코드:
-```html
-
-
-
+
```
-## 카테고리 차트
-
-Dreamhack의 워게임 카테고리별 점수를 원형 차트로 표시합니다. 각 카테고리별 점수와 랭킹을 확인할 수 있습니다.
+### Most Solved Categories
-### 사용 방법
-
-#### Markdown
```markdown
-
+
```
-#### HTML
-```html
-
-
-
-```
+> 💡 클릭 시 Dreamhack 프로필로 이동하게 하려면 HTML 사용:
+> ```html
+>
+>
+>
+> ```
-### 예시
+---
-
+## Features
-## 기술 스택
+| Feature | Description |
+|---------|-------------|
+| **Wargame Stats** | 해결한 문제 수, 랭킹, 점수, TOP % 표시 |
+| **Category Chart** | 카테고리별 점수 분포를 파이 차트로 시각화 |
+| **Auto Update** | 실시간으로 최신 통계 반영 |
+| **Caching** | Redis 캐싱으로 빠른 응답 속도 |
-- Next.js
-- TypeScript
-- Node.js
-- Redis (캐싱)
+---
-## 로컬에서 실행하기
+## Local Development
-1. 저장소 클론
-```
-git clone https://github.com/yourusername/dreamhack-readme-stats.git
+```bash
+# 저장소 클론
+git clone https://github.com/with-developer/dreamhack-readme-stats.git
cd dreamhack-readme-stats
-```
-2. 의존성 설치
-```
+# 의존성 설치
npm install
-```
-
-3. 개발 서버 실행
-```
-npm run dev
-```
-
-4. 브라우저에서 확인
-```
-http://localhost:3000
-```
-
-## Redis 캐싱 설정
-성능 향상을 위해 Redis 캐싱을 사용합니다. 사용자 ID 조회 결과를 캐싱하여 API 응답 시간을 크게 단축합니다.
-
-### 로컬 환경에서 Redis 설정하기
-
-1. `.env.local.example` 파일을 `.env.local`로 복사합니다.
-```
+# 환경변수 설정
cp .env.local.example .env.local
+
+# 개발 서버 실행
+npm run dev
```
-2. `.env.local` 파일을 편집하여 Redis 연결 정보를 설정합니다.
+http://localhost:3000 에서 확인
-Redis 연결은 두 가지 방법으로 설정할 수 있습니다:
+### Environment Variables
-#### 방법 1: REDIS_URL 사용 (권장)
-```
-REDIS_URL=redis://username:password@host:port
-```
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `REDIS_URL` | No | Redis 연결 URL (캐싱용) |
+| `GITHUB_TOKEN` | No | GitHub API 토큰 (사용자 수 집계용) |
-#### 방법 2: 개별 설정 사용
-```
-REDIS_HOST=localhost
-REDIS_PORT=6379
-REDIS_USERNAME=default
-REDIS_PASSWORD=your_password
-REDIS_TLS=false
-```
+---
-### Redis 서비스 제공업체
+## Tech Stack
-다음과 같은 Redis 서비스를 사용할 수 있습니다:
+- **Framework**: Next.js 14
+- **Language**: TypeScript
+- **Cache**: Redis (Upstash)
+- **Deploy**: Vercel
-- [Upstash](https://upstash.com/) - 서버리스 Redis (무료 티어 제공)
-- [Redis Cloud](https://redis.com/redis-enterprise-cloud/overview/) - 관리형 Redis 서비스
-- 로컬 Redis 서버
+---
-### Redis 없이 실행하기
+## License
-Redis 설정이 없어도 애플리케이션은 정상적으로 작동합니다. 다만, 캐싱 기능이 비활성화되어 모든 요청이 Dreamhack API를 직접 호출하게 됩니다.
\ No newline at end of file
+MIT License
diff --git a/src/__tests__/pages/api/users-count.test.ts b/src/__tests__/pages/api/users-count.test.ts
new file mode 100644
index 0000000..df00f09
--- /dev/null
+++ b/src/__tests__/pages/api/users-count.test.ts
@@ -0,0 +1,92 @@
+import { createMocks } from 'node-mocks-http';
+import handler from '../../../pages/api/users-count';
+
+// fetch 모킹
+global.fetch = jest.fn();
+
+describe('users-count API 엔드포인트 테스트', () => {
+ const originalEnv = process.env;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ process.env = { ...originalEnv, GITHUB_TOKEN: 'test-token' };
+ });
+
+ afterEach(() => {
+ process.env = originalEnv;
+ });
+
+ it('고유 사용자 수를 shields.io 형식으로 반환해야 함', async () => {
+ const mockResponse = {
+ total_count: 3,
+ items: [
+ { repository: { owner: { login: 'user1' } } },
+ { repository: { owner: { login: 'user2' } } },
+ { repository: { owner: { login: 'user1' } } }, // 중복 사용자
+ ]
+ };
+
+ (global.fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ });
+
+ const { req, res } = createMocks({
+ method: 'GET',
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(200);
+ const data = JSON.parse(res._getData());
+ expect(data).toEqual({
+ schemaVersion: 1,
+ label: 'users',
+ message: '2', // 고유 사용자 2명
+ color: 'blue'
+ });
+ });
+
+ it('GITHUB_TOKEN이 없으면 500 에러를 반환해야 함', async () => {
+ delete process.env.GITHUB_TOKEN;
+
+ const { req, res } = createMocks({
+ method: 'GET',
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(500);
+ expect(JSON.parse(res._getData())).toEqual({ error: 'GitHub token not configured' });
+ });
+
+ it('GitHub API 에러 시 해당 상태 코드를 반환해야 함', async () => {
+ (global.fetch as jest.Mock).mockResolvedValueOnce({
+ ok: false,
+ status: 403,
+ text: async () => 'Rate limit exceeded'
+ });
+
+ const { req, res } = createMocks({
+ method: 'GET',
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(403);
+ expect(JSON.parse(res._getData())).toEqual({ error: 'GitHub API error' });
+ });
+
+ it('fetch 예외 발생 시 500 에러를 반환해야 함', async () => {
+ (global.fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
+
+ const { req, res } = createMocks({
+ method: 'GET',
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(500);
+ expect(JSON.parse(res._getData())).toEqual({ error: 'Internal server error' });
+ });
+});
diff --git a/src/pages/api/users-count.ts b/src/pages/api/users-count.ts
new file mode 100644
index 0000000..9c5e8ad
--- /dev/null
+++ b/src/pages/api/users-count.ts
@@ -0,0 +1,62 @@
+import { NextApiRequest, NextApiResponse } from 'next';
+
+interface SearchItem {
+ repository: {
+ owner: {
+ login: string;
+ };
+ };
+}
+
+interface SearchResponse {
+ total_count: number;
+ items: SearchItem[];
+}
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ const githubToken = process.env.GITHUB_TOKEN;
+
+ if (!githubToken) {
+ return res.status(500).json({ error: 'GitHub token not configured' });
+ }
+
+ try {
+ const query = encodeURIComponent('"dreamhack-readme-stats.vercel.app/api/" in:file filename:README.md');
+ const response = await fetch(
+ `https://api.github.com/search/code?q=${query}&per_page=100`,
+ {
+ headers: {
+ 'Authorization': `Bearer ${githubToken}`,
+ 'Accept': 'application/vnd.github+json',
+ 'X-GitHub-Api-Version': '2022-11-28'
+ }
+ }
+ );
+
+ if (!response.ok) {
+ const error = await response.text();
+ console.error('GitHub API error:', error);
+ return res.status(response.status).json({ error: 'GitHub API error' });
+ }
+
+ const data: SearchResponse = await response.json();
+
+ // 고유 사용자 수 계산
+ const uniqueUsers = new Set(
+ data.items.map(item => item.repository.owner.login)
+ );
+ const count = uniqueUsers.size;
+
+ // shields.io endpoint format
+ res.setHeader('Cache-Control', 'public, max-age=3600, s-maxage=3600');
+ return res.status(200).json({
+ schemaVersion: 1,
+ label: 'users',
+ message: String(count),
+ color: 'blue'
+ });
+ } catch (error) {
+ console.error('Error fetching user count:', error);
+ return res.status(500).json({ error: 'Internal server error' });
+ }
+}