Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add draft for data module #28

Merged
merged 128 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
c5210c0
Feat: add draft for data module
HC-kang Oct 22, 2024
1cf4c52
fix: type erros with jest-dom
DaleSeo Oct 23, 2024
56e239f
ci: add integration workflow
DaleSeo Oct 23, 2024
3f79d97
chore: do not clutter test output with errors
DaleSeo Oct 23, 2024
02b602b
fix: global css for storybook
Sunjae95 Oct 23, 2024
31bbf09
Merge pull request #32 from DaleStudy/30-storybook-global-css
Sunjae95 Oct 23, 2024
feaaa8f
Merge pull request #29 from DaleStudy/ci-integration
DaleSeo Oct 23, 2024
38021c1
feat : implement progress component
SamTheKorean Oct 23, 2024
d8f96be
feat : implement progress story
SamTheKorean Oct 23, 2024
9f163b7
feat : implement progress test
SamTheKorean Oct 23, 2024
6fbfe7a
feat : implement index page
SamTheKorean Oct 23, 2024
e6590c1
fix : refactor test cases and remove unused dependancy
SamTheKorean Oct 25, 2024
d4639a7
Merge pull request #31 from DaleStudy/sam/create-progress-page
SamTheKorean Oct 25, 2024
2378ee2
fix : clean up css not required
SamTheKorean Oct 20, 2024
0c95ee6
feat : implement leaderboard component
SamTheKorean Oct 21, 2024
7069566
feat : implement story for leaderboard
SamTheKorean Oct 21, 2024
ed91ab0
feat : implement unit test for leaderboard
SamTheKorean Oct 21, 2024
f13112a
feat : implement index page
SamTheKorean Oct 23, 2024
8b0ba70
fix : refactor test and remove unused imports
SamTheKorean Oct 25, 2024
ee0c9cc
ci: add deployment workflow
DaleSeo Oct 25, 2024
86bb7ca
feat: clean up member data module
HC-kang Oct 26, 2024
f5dec9c
fix : apply feadback with test cases and htnl structure
SamTheKorean Oct 26, 2024
ddb16ec
fix: 데이터 모듈에서 동일한 멤버가 여러 팀에 속한 경우 중복 제거
HC-kang Oct 26, 2024
65f05cc
test: add test code for get data service and clients
HC-kang Oct 26, 2024
204e9de
refactor: move types to each directory
HC-kang Oct 26, 2024
2ffaef9
fix: data module type import error
HC-kang Oct 26, 2024
6a21ba6
remove: unused file
HC-kang Oct 26, 2024
fda3341
test: replace console error mock with spy
HC-kang Oct 26, 2024
9a43193
Merge pull request #34 from DaleStudy/ci-deployment
DaleSeo Oct 27, 2024
363ff4a
docs: contributing and pr template
DaleSeo Oct 27, 2024
969f60d
fix : adjust folder and filenames for convention
SamTheKorean Oct 28, 2024
0865793
Merge pull request #17 from DaleStudy/sam/create-leaderboard-page
SamTheKorean Oct 28, 2024
4230f14
test: Certificate page
Sunjae95 Oct 19, 2024
b8322bf
story: Certificate Page
Sunjae95 Oct 19, 2024
cf784b1
rename: certificate from src to components
Sunjae95 Oct 19, 2024
bfeba2d
fix: review apply
Sunjae95 Oct 19, 2024
a6c9946
Merge pull request #18 from DaleStudy/15-certificate-only-frame
Sunjae95 Oct 28, 2024
1798d60
Refactor: modify format and types without changing test cases
HC-kang Oct 28, 2024
8b50286
Merge pull request #40 from DaleStudy/docs-contributing
DaleSeo Oct 28, 2024
d2ee200
Refactor: encapsulate fetch client
HC-kang Oct 28, 2024
d405e1b
Rename: API_VERSION to MEDIA_TYPE
HC-kang Oct 28, 2024
e4af42b
Refactor: modify handle error function
HC-kang Oct 28, 2024
ef78a24
Test: enhance test cases
HC-kang Oct 28, 2024
21ee78c
separate documents and scripts per page
DaleSeo Oct 28, 2024
dfe5b0c
configure vite to support mpa
DaleSeo Oct 28, 2024
044e63b
deps: remove react-router-dom
DaleSeo Oct 29, 2024
d140a4d
feat: remove ErrorPage
DaleSeo Oct 29, 2024
c31a5b5
test: address broken tests
DaleSeo Oct 30, 2024
c33c3cd
remove progress folder
DaleSeo Oct 30, 2024
9f8b2d0
create Progress folder
DaleSeo Oct 30, 2024
212435d
Merge pull request #44 from DaleStudy/mpa
DaleSeo Oct 30, 2024
86e9e2c
remove: unused variables
HC-kang Oct 30, 2024
24866ca
feat: add attr to MemberInfo and rename GithubInfo to StudyInfo
HC-kang Oct 30, 2024
b7f5ab4
refactor: createMemberInfoMap function
HC-kang Oct 30, 2024
dd84127
fix: type import
HC-kang Nov 1, 2024
b47f411
remove: fetch client
HC-kang Nov 1, 2024
9cc85bf
simplify: github client
HC-kang Nov 1, 2024
0c2962d
simplify: member info service
HC-kang Nov 1, 2024
7d577ff
remove: legacy codes
HC-kang Nov 1, 2024
6c94c93
refactor: modify directory structure and divide types
HC-kang Nov 2, 2024
f36372b
test: add tests for modules
HC-kang Nov 2, 2024
4922cc5
feat: add store service
HC-kang Nov 2, 2024
3837225
test: fix side effects of type simplification
HC-kang Nov 2, 2024
b1eb0ed
test: add store service test
HC-kang Nov 2, 2024
b561ba1
feat: add store service local storage cache
HC-kang Nov 2, 2024
db3e480
feat: add hard refresh to get data
HC-kang Nov 2, 2024
a163cb3
chore: cleanup directory structure
HC-kang Nov 2, 2024
4946eaf
typo
HC-kang Nov 2, 2024
09a8ba8
rename: modify file name and remove cache
HC-kang Nov 2, 2024
38d492f
remove: renamed file
HC-kang Nov 2, 2024
90197a9
feat: add renamed file
HC-kang Nov 2, 2024
747d927
rename: rename file
HC-kang Nov 2, 2024
ec2c708
rename: rename file
HC-kang Nov 2, 2024
2d66dc7
rename: github directory
HC-kang Nov 2, 2024
4f57007
rename: gitHub directory
HC-kang Nov 2, 2024
bb3dc47
remove: caching feature in store service
HC-kang Nov 3, 2024
f7c7eb1
style: arrow to function for exported functions
HC-kang Nov 3, 2024
453464f
test: fix mock type safety
HC-kang Nov 3, 2024
6825e8d
remove: unused features
HC-kang Nov 3, 2024
1f3f11c
test: update test for feature change
HC-kang Nov 3, 2024
d814bca
refactor: apply review comments
HC-kang Nov 5, 2024
0d0747d
test: remove invalid cohort test
HC-kang Nov 5, 2024
b3ab1d4
Feat: add draft for data module
HC-kang Oct 22, 2024
98ff3cc
feat: clean up member data module
HC-kang Oct 26, 2024
7e8b9d2
fix: 데이터 모듈에서 동일한 멤버가 여러 팀에 속한 경우 중복 제거
HC-kang Oct 26, 2024
a25315f
test: add test code for get data service and clients
HC-kang Oct 26, 2024
1abdff9
refactor: move types to each directory
HC-kang Oct 26, 2024
2b9d62d
fix: data module type import error
HC-kang Oct 26, 2024
acbb811
remove: unused file
HC-kang Oct 26, 2024
3531a92
test: replace console error mock with spy
HC-kang Oct 26, 2024
606ef44
Refactor: modify format and types without changing test cases
HC-kang Oct 28, 2024
e2249dd
Refactor: encapsulate fetch client
HC-kang Oct 28, 2024
a2252ed
Rename: API_VERSION to MEDIA_TYPE
HC-kang Oct 28, 2024
0577a41
Refactor: modify handle error function
HC-kang Oct 28, 2024
6edad94
Test: enhance test cases
HC-kang Oct 28, 2024
8ac9720
remove: unused variables
HC-kang Oct 30, 2024
72f710b
feat: add attr to MemberInfo and rename GithubInfo to StudyInfo
HC-kang Oct 30, 2024
842aa44
refactor: createMemberInfoMap function
HC-kang Oct 30, 2024
a37e7a1
fix: type import
HC-kang Nov 1, 2024
d295ca2
remove: fetch client
HC-kang Nov 1, 2024
e78f71a
simplify: github client
HC-kang Nov 1, 2024
ce89bd5
simplify: member info service
HC-kang Nov 1, 2024
c28a759
remove: legacy codes
HC-kang Nov 1, 2024
0370bd0
refactor: modify directory structure and divide types
HC-kang Nov 2, 2024
fc49ddf
test: add tests for modules
HC-kang Nov 2, 2024
8f8c5a3
feat: add store service
HC-kang Nov 2, 2024
265f97b
test: fix side effects of type simplification
HC-kang Nov 2, 2024
8305e9b
test: add store service test
HC-kang Nov 2, 2024
57e9ff3
feat: add store service local storage cache
HC-kang Nov 2, 2024
2ae8ade
feat: add hard refresh to get data
HC-kang Nov 2, 2024
77ac38d
chore: cleanup directory structure
HC-kang Nov 2, 2024
e39bbde
typo
HC-kang Nov 2, 2024
2048e99
rename: modify file name and remove cache
HC-kang Nov 2, 2024
fadf497
remove: renamed file
HC-kang Nov 2, 2024
f4806d6
feat: add renamed file
HC-kang Nov 2, 2024
049de21
rename: rename file
HC-kang Nov 2, 2024
3319125
rename: rename file
HC-kang Nov 2, 2024
2498290
rename: github directory
HC-kang Nov 2, 2024
dd44373
rename: gitHub directory
HC-kang Nov 2, 2024
b298f45
remove: caching feature in store service
HC-kang Nov 3, 2024
1c4aae0
style: arrow to function for exported functions
HC-kang Nov 3, 2024
6f70aa0
test: fix mock type safety
HC-kang Nov 3, 2024
7bbd017
remove: unused features
HC-kang Nov 3, 2024
ff22c69
test: update test for feature change
HC-kang Nov 3, 2024
618236a
refactor: apply review comments
HC-kang Nov 5, 2024
b7ef969
test: remove invalid cohort test
HC-kang Nov 5, 2024
dafdbfd
typo: 오탈자 수정
HC-kang Nov 7, 2024
31e28ac
Merge branch 'feature/25-add-data-retrieve-module' of https://github.…
HC-kang Nov 7, 2024
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
16 changes: 16 additions & 0 deletions src/api/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const GITHUB_API_BASE_URL = 'https://api.github.com';
export const TOTAL_PROBLEMS = 75;

export const ALTERNATIVE_IDS: Record<string, string> = {
// 1기
meoooh: 'han',
koreas9408: 'seunghyun-lim',
leokim0922: 'leo',

// 2기
obzva: 'flynn',
'kim-young': 'kimyoung',
kjb512: 'kayden',
lymchgmk: 'egon',
jeonghwanmin: 'hwanmini',
};
42 changes: 42 additions & 0 deletions src/api/getMembersByCohort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ALTERNATIVE_IDS, GITHUB_API_BASE_URL } from './const';
import { Cohort, CohortInfo } from './types';
import { fetchWithCache } from './utils';

const GITHUB_TOKEN = 'process.env.GITHUB_TOKEN'; // TODO: Github Token을 환경변수로 설정

export async function getMembersByCohort(cohort: number): Promise<CohortInfo> {
return fetchWithCache(`members_cohort_${cohort}`, async () => {
const teamName = ['', 'leetcode01', 'leetcode']; // TODO: getTeams 에서 가져와 주입 받도록 수정
const url = `/orgs/DaleStudy/teams/${teamName[cohort]}/members`;
const headers = {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${GITHUB_TOKEN}`,
};

try {
const res = await fetch(GITHUB_API_BASE_URL + url, { headers });

if (!res.ok)
throw new Error(`Failed to fetch members: ${res.statusText}`);

const data = await res.json();
return {
cohort: cohort as Cohort,
totalMembers: data.length,
members: data.map((member: { login: string }) => ({
id:
ALTERNATIVE_IDS[member.login.toLowerCase()] || // 파일명이 Github 계정과 다른 경우
member.login.toLowerCase(),
name: member.login,
})),
};
} catch (err) {
if (err instanceof Error) {
console.error(`Error fetching cohort members: ${err.message}`);
} else {
console.error('Error fetching cohort members:', err);
}
return { cohort: cohort as Cohort, totalMembers: 0, members: [] };
}
});
}
31 changes: 31 additions & 0 deletions src/api/getRepoDirectoryData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { GITHUB_API_BASE_URL } from './const';
import { RepositoryTree } from './types';
import { fetchWithCache } from './utils';

export async function getRepositoryDirectoryData(): Promise<RepositoryTree[]> {
return fetchWithCache('repository_directory', async () => {
const url = `/repos/DaleStudy/leetcode-study/git/trees/main?recursive=1`; // recursive=1로 충분하다.
const headers = {
Accept: 'application/vnd.github+json',
};

try {
const res = await fetch(GITHUB_API_BASE_URL + url, { headers });

if (!res.ok)
throw new Error(
`Failed to fetch repository directory: ${res.statusText}`
);

const data = await res.json();
return data.tree;
} catch (err) {
if (err instanceof Error) {
console.error(`Error fetching repository directory: ${err.message}`);
} else {
console.error('Error fetching repository directory:', err);
}
return [];
}
});
}
117 changes: 117 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { RepositoryTree, SubmissionOfMember, SubmissionPath } from './types';
import { getMembersByCohort } from './getMembersByCohort';
import { getRepositoryDirectoryData } from './getRepoDirectoryData';
import { TOTAL_PROBLEMS } from './const';

/**
* 전체 트리에서 필요한 데이터만 추출
* 블롭이면서 /를 포함하는 데이터
*/
function extractRelevantData(data: RepositoryTree[]): SubmissionPath[] {
return data
.filter((item) => item.type === 'blob')
.filter((item) => item.path.includes('/'))
.map((item) => item.path.toLowerCase() as SubmissionPath);
}

/**
* 제출 경로를 분석하여 필요한 정보를 추출
*/
function parseSubmissionPath(path: SubmissionPath): {
memberId: string;
problemTitle: string;
language: string;
} {
const regex = /^([^/]+)\/([^.]+)\.([a-zA-Z0-9]+)$/;
const match = path.match(regex);

if (match) {
const problemTitle = match[1];
const memberId = match[2];
const language = match[3];
return { memberId, problemTitle, language };
}
return { memberId: '', problemTitle: '', language: '' };
}

/**
* 각 멤버별 제출 현황 디스플레이
*/
function displayProgress(
memberMap: Record<string, SubmissionOfMember>
): string[] {
const progressDisplay: string[] = [];
const maxMemberIdLength = Math.max(
...Object.values(memberMap).map((member) => member.memberId.length)
);
Object.values(memberMap).forEach((member) => {
const progressPercentage = (member.totalSubmissions / TOTAL_PROBLEMS) * 100;
const progressBarLength = Math.ceil(progressPercentage / 2);
const progressBar = '█'.repeat(progressBarLength).padEnd(50, ' ');

progressDisplay.push(
`${member.memberId.padEnd(maxMemberIdLength)} | ${progressBar} | ${
member.totalSubmissions
}/${TOTAL_PROBLEMS} (${progressPercentage.toFixed(2)}%)`
);
});
return progressDisplay;
}

/**
* 주어진 기수별 제출 현황을 출력한다.
*/
export async function printProcess(cohort: number) {
try {
// TODO: getTeams 추가
// 필요한 데이터 조회
const [membersOfCohort, repositoryDirectory] = await Promise.all([
getMembersByCohort(cohort),
getRepositoryDirectoryData(),
]);

// 멤버별 제출 정보를 담을 객체 생성
const memberMap: Record<string, SubmissionOfMember> = {};
membersOfCohort.members.forEach((member) => {
memberMap[member.id] = {
memberId: member.id,
totalSubmissions: 0,
submissions: [],
};
});

// 제출 정보를 memberMap에 저장
const relevantData = extractRelevantData(repositoryDirectory);
relevantData.forEach((path) => {
const { memberId, problemTitle, language } = parseSubmissionPath(path);
if (memberMap[memberId]) {
if ( // 다수의 언어로 제출한 경우 중복 제출로 간주하지 않음
!memberMap[memberId].submissions.some(
(s) => s.problemTitle === problemTitle
)
) {
memberMap[memberId].totalSubmissions += 1;
}
memberMap[memberId].submissions.push({
memberId,
problemTitle,
language,
});
}
});

const progressArray = displayProgress(memberMap);
const progressString = progressArray.join('\n'); // 문자열로 변환

console.log(progressString);
} catch (error) {
if (error instanceof Error) {
console.error(`An error occurred: ${error.message}`);
} else {
console.error('An error occurred:', error);
}
}
}

// 전체 함수 호출
await printProcess(2);
35 changes: 35 additions & 0 deletions src/api/types.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

타입을 이렇게 하나의 파일로 중앙화 시키셨네요. 코드를 읽기 좀 더 수월하도록 함수 근처에 타입을 두는 것도 고려해보셨으면 좋겠습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export type Cohort = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;

export type CohortInfo = {
cohort: Cohort;
totalMembers: number;
members: Member[];
};

export type Member = {
id: string;
name: string;
};

export type RepositoryTree = {
path: string;
mode: string;
type: string;
sha: string;
url: string;
size: number;
};

export type SubmissionPath = string;

export type Submission = {
memberId: string;
problemTitle: string;
language: string;
};

export type SubmissionOfMember = {
memberId: string;
totalSubmissions: number;
submissions: Submission[];
};
66 changes: 66 additions & 0 deletions src/api/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 캐시를 저장할 객체
const cache: Record<string, { data: unknown; timestamp: number }> = {};
const CACHE_DURATION = 10 * 60 * 1000; // 10분 (밀리초 단위)

// 캐시를 이용한 데이터 요청 함수
export async function fetchWithCache<T>(
key: string,
fetchFunction: () => Promise<T>
): Promise<T> {
const currentTime = Date.now();
const cached = cache[key];

// 캐시가 존재하고 만료되지 않았다면 데이터 반환
if (cached) {
if (currentTime - cached.timestamp < CACHE_DURATION) {
console.log(
`${new Date(
currentTime
).toISOString()} - Returning cached data for key: ${key}`
);
return cached.data as T;
} else {
// 만료된 캐시는 삭제
console.log(
`${new Date(
currentTime
).toISOString()} - Cache expired for key: ${key}, removing from cache`
);
delete cache[key];
}
}

// API 호출 후 새로 캐시에 저장
const data = await fetchFunction();
cache[key] = { data, timestamp: currentTime };
console.log(
`${new Date(currentTime).toISOString()} - Fetched new data for key: ${key}`
);
return data;
}

// 요청을 추적하기 위한 캐시
const rateLimitCache: Record<string, { count: number; timestamp: number }> = {};
const RATE_LIMIT_DURATION = 60 * 1000; // 1분 (밀리초 단위)
const MAX_REQUESTS_PER_MINUTE = 60; // 1분당 최대 60회 요청

// 레이트 리미트 체크 함수
export function isRateLimited(ip: string): boolean {
const currentTime = Date.now();
const cacheEntry = rateLimitCache[ip];

// 캐시에 IP가 없거나, 제한 시간이 지난 경우 초기화
if (!cacheEntry || currentTime - cacheEntry.timestamp > RATE_LIMIT_DURATION) {
rateLimitCache[ip] = { count: 1, timestamp: currentTime };
return false;
}

// 요청 횟수를 초과한 경우
if (cacheEntry.count >= MAX_REQUESTS_PER_MINUTE) {
return true;
}

// 요청 횟수를 증가시킴
cacheEntry.count += 1;
return false;
}