Skip to content

Commit

Permalink
feat: clean up member data module
Browse files Browse the repository at this point in the history
- Reorganize file structure for better maintainability
- Add error handling
- Separate client interfaces and implementations
  • Loading branch information
HC-kang committed Oct 26, 2024
1 parent c5210c0 commit 86bb7ca
Show file tree
Hide file tree
Showing 15 changed files with 448 additions and 303 deletions.
25 changes: 25 additions & 0 deletions src/api/common.ts
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",
};
12 changes: 12 additions & 0 deletions src/api/config/index.ts
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;
16 changes: 0 additions & 16 deletions src/api/const.ts

This file was deleted.

42 changes: 0 additions & 42 deletions src/api/getMembersByCohort.ts

This file was deleted.

31 changes: 0 additions & 31 deletions src/api/getRepoDirectoryData.ts

This file was deleted.

124 changes: 11 additions & 113 deletions src/api/index.ts
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);
85 changes: 85 additions & 0 deletions src/api/services/github/GithubApiClient.ts
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());
}, "");
}
}
12 changes: 12 additions & 0 deletions src/api/services/github/interfaces.ts
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[]>;
}
Loading

0 comments on commit 86bb7ca

Please sign in to comment.