-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Reorganize file structure for better maintainability - Add error handling - Separate client interfaces and implementations
- Loading branch information
Showing
15 changed files
with
448 additions
and
303 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
export function handleError(message: string, error: unknown): void { | ||
if (error instanceof Error) { | ||
console.error(`${message}: ${error.message}`); | ||
} else { | ||
console.error(message, error); | ||
} | ||
} | ||
|
||
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", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const CONFIG = { | ||
study: { | ||
orgName: "DaleStudy", | ||
repoOwner: "DaleStudy", | ||
repoName: "leetcode-study", | ||
branchName: "main", | ||
teamPrefix: "leetcode", | ||
}, | ||
github: { | ||
token: process.env.GITHUB_TOKEN ?? "", | ||
}, | ||
} as const; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,15 @@ | ||
import { RepositoryTree, SubmissionOfMember, SubmissionPath } from './types'; | ||
import { getMembersByCohort } from './getMembersByCohort'; | ||
import { getRepositoryDirectoryData } from './getRepoDirectoryData'; | ||
import { TOTAL_PROBLEMS } from './const'; | ||
import { CONFIG } from "./config"; | ||
import { GithubApiClient } from "./services/github/GithubApiClient"; | ||
import { MemberInfoService } from "./services/memberInfo/MemberInfoService"; | ||
import { FetchClient } from "./utils/FetchClient"; | ||
|
||
/** | ||
* 전체 트리에서 필요한 데이터만 추출 | ||
* 블롭이면서 /를 포함하는 데이터 | ||
*/ | ||
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'); // 문자열로 변환 | ||
async function main() { | ||
const fetchClient = new FetchClient(); | ||
const githubClient = new GithubApiClient(fetchClient, CONFIG.github.token); | ||
const memberService = new MemberInfoService(githubClient, CONFIG.study); | ||
|
||
console.log(progressString); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error(`An error occurred: ${error.message}`); | ||
} else { | ||
console.error('An error occurred:', error); | ||
} | ||
} | ||
const { data } = await memberService.getMemberInfo(); | ||
console.log(data.slice(0, 5)); | ||
} | ||
|
||
// 전체 함수 호출 | ||
await printProcess(2); | ||
main().catch(console.error); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { IFetchClient } from "../../utils/interfaces"; | ||
import { IGithubApiClient } from "./interfaces"; | ||
import { | ||
GithubMember, | ||
GithubTeam, | ||
GithubTree, | ||
GithubTreeResponse, | ||
} from "../../types"; | ||
import { handleError } from "../../common"; | ||
|
||
export class GithubApiClient implements IGithubApiClient { | ||
private static readonly BASE_URL = "https://api.github.com"; | ||
private static readonly API_VERSION = "application/vnd.github.v3+json"; | ||
|
||
constructor( | ||
private readonly fetchClient: IFetchClient, | ||
private readonly githubToken: string, | ||
) { | ||
this.initializeClient(); | ||
} | ||
|
||
async getTeams(orgName: string): Promise<GithubTeam[]> { | ||
return this.executeRequest<GithubTeam[]>( | ||
this.buildUrl`/orgs/${orgName}/teams`, | ||
`Failed to fetch teams for organization: ${orgName}`, | ||
); | ||
} | ||
|
||
async getTeamMembers( | ||
orgName: string, | ||
teamName: string, | ||
): Promise<GithubMember[]> { | ||
return this.executeRequest<GithubMember[]>( | ||
this.buildUrl`/orgs/${orgName}/teams/${teamName}/members`, | ||
`Failed to fetch members for team: ${teamName} in organization: ${orgName}`, | ||
); | ||
} | ||
|
||
async getDirectoryTree( | ||
owner: string, | ||
repo: string, | ||
treeSha: string, | ||
recursive = 1, | ||
): Promise<GithubTree[]> { | ||
const response = await this.executeRequest<GithubTreeResponse>( | ||
this | ||
.buildUrl`/repos/${owner}/${repo}/git/trees/${treeSha}?recursive=${recursive}`, | ||
`Failed to fetch directory tree for repository: ${repo} owned by: ${owner}`, | ||
); | ||
|
||
return response.tree; | ||
} | ||
|
||
private initializeClient(): void { | ||
if (!this.githubToken) { | ||
throw new Error("GitHub token is required but not provided"); | ||
} | ||
|
||
this.fetchClient.setBaseUrl(GithubApiClient.BASE_URL).setBaseHeaders([ | ||
["Accept", GithubApiClient.API_VERSION], | ||
["Authorization", `Bearer ${this.githubToken}`], | ||
]); | ||
} | ||
|
||
private async executeRequest<T>( | ||
url: string, | ||
errorContext: string, | ||
): Promise<T> { | ||
try { | ||
return await this.fetchClient.get<T>(url); | ||
} catch (error) { | ||
throw handleError(errorContext, error); | ||
} | ||
} | ||
|
||
private buildUrl( | ||
strings: TemplateStringsArray, | ||
...values: (string | number)[] | ||
): string { | ||
return strings.reduce((result, str, i) => { | ||
const value = values[i] ?? ""; | ||
return result + str + encodeURIComponent(value.toString()); | ||
}, ""); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { GithubTeam, GithubMember, GithubTree } from "../../types"; | ||
|
||
export interface IGithubApiClient { | ||
getTeams(orgName: string): Promise<GithubTeam[]>; | ||
getTeamMembers(orgName: string, teamName: string): Promise<GithubMember[]>; | ||
getDirectoryTree( | ||
owner: string, | ||
repo: string, | ||
treeSha: string, | ||
recursive?: number, | ||
): Promise<GithubTree[]>; | ||
} |
Oops, something went wrong.