mirror of
https://github.com/khairul169/github-leaderboard.git
synced 2026-09-17 09:53:21 +07:00
feat: initial commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import db from "@server/db";
|
||||
import { repositories, users } from "@server/models";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
type CalculateUserPointsType = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
const weights = {
|
||||
followers: 20,
|
||||
following: 10,
|
||||
achievements: 100,
|
||||
repositories: 1,
|
||||
contributorsAmount: 25,
|
||||
stars: 10,
|
||||
forks: 10,
|
||||
languagesKnown: 50,
|
||||
commits: 1,
|
||||
lineOfCodes: 0.01,
|
||||
};
|
||||
|
||||
export const calculateUserPoints = async (data: CalculateUserPointsType) => {
|
||||
const [user] = await db.select().from(users).where(eq(users.id, data.userId));
|
||||
if (!user) {
|
||||
throw new Error("User not found!");
|
||||
}
|
||||
|
||||
let points = 0;
|
||||
let totalCommits = 0;
|
||||
let totalLineOfCodes = 0;
|
||||
|
||||
// User statistics
|
||||
points += user.followers * weights.followers;
|
||||
points += user.following * weights.following;
|
||||
points += user.achievements
|
||||
? user.achievements?.length * weights.achievements
|
||||
: 0;
|
||||
|
||||
// User repositories
|
||||
const repos = await db
|
||||
.select()
|
||||
.from(repositories)
|
||||
.where(eq(repositories.userId, user.id));
|
||||
points += repos.length * weights.repositories;
|
||||
|
||||
// Languages known
|
||||
const languages = new Set(
|
||||
repos.flatMap((i) => i.languages?.map((j) => j.lang))
|
||||
);
|
||||
points += languages.size * weights.languagesKnown;
|
||||
|
||||
// Activities
|
||||
repos.forEach((repo) => {
|
||||
const contributors = repo.contributors?.filter(
|
||||
(i) => i.author?.login !== user.username
|
||||
);
|
||||
points += contributors
|
||||
? contributors.length * weights.contributorsAmount
|
||||
: 0;
|
||||
|
||||
points += repo.stars * weights.stars;
|
||||
points += repo.forks * weights.forks;
|
||||
|
||||
const contrib = repo.contributors?.find(
|
||||
(i) => i.author?.login === user.username
|
||||
);
|
||||
const commits = contrib?.commits || 0;
|
||||
const lineOfCodes = contrib?.additions || 0;
|
||||
|
||||
points += commits * weights.commits;
|
||||
points += lineOfCodes * weights.lineOfCodes;
|
||||
totalCommits += commits;
|
||||
totalLineOfCodes += lineOfCodes;
|
||||
});
|
||||
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
points: Math.round(points),
|
||||
commits: totalCommits,
|
||||
lineOfCodes: totalLineOfCodes,
|
||||
})
|
||||
.where(eq(users.id, user.id));
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import db from "@server/db";
|
||||
import github from "@server/lib/github";
|
||||
import queue from "@server/lib/queue";
|
||||
import { repositories, users } from "@server/models";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type FetchRepoContributorsType = {
|
||||
id: number;
|
||||
uri: string;
|
||||
};
|
||||
|
||||
export const fetchRepoContributors = async (
|
||||
data: FetchRepoContributorsType
|
||||
) => {
|
||||
const [repo] = await db
|
||||
.select({ id: repositories.id, userAccessToken: users.accessToken })
|
||||
.from(repositories)
|
||||
.innerJoin(users, eq(users.id, repositories.userId))
|
||||
.where(eq(repositories.id, data.id));
|
||||
|
||||
if (!repo) {
|
||||
throw new Error("Repository not found!");
|
||||
}
|
||||
|
||||
if (!repo.userAccessToken) {
|
||||
throw new Error("User access token not found!");
|
||||
}
|
||||
|
||||
const contributors = await github.getRepoContributors(data.uri, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${repo.userAccessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
const [result] = await db
|
||||
.update(repositories)
|
||||
.set({ contributors })
|
||||
.where(eq(repositories.id, data.id))
|
||||
.returning();
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Cannot update repository!");
|
||||
}
|
||||
|
||||
await queue.add("calculateUserPoints", { userId: result.userId });
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import db from "@server/db";
|
||||
import github from "@server/lib/github";
|
||||
import queue from "@server/lib/queue";
|
||||
import { repositories } from "@server/models";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type FetchRepoDataJobType = {
|
||||
id: number;
|
||||
uri: string;
|
||||
};
|
||||
|
||||
export const fetchRepoData = async (data: FetchRepoDataJobType) => {
|
||||
const details = await github.getRepoDetails(data.uri);
|
||||
|
||||
const [result] = await db
|
||||
.update(repositories)
|
||||
.set({ languages: details.languages })
|
||||
.where(eq(repositories.id, data.id))
|
||||
.returning();
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Repository not found!");
|
||||
}
|
||||
|
||||
await queue.add("calculateUserPoints", { userId: result.userId });
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import db from "@server/db";
|
||||
import github from "@server/lib/github";
|
||||
import queue from "@server/lib/queue";
|
||||
import { users } from "@server/models";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export type FetchUserProfileType = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const fetchUserProfile = async (data: FetchUserProfileType) => {
|
||||
const [user] = await db.select().from(users).where(eq(users.id, data.userId));
|
||||
if (!user) {
|
||||
throw new Error("User not found!");
|
||||
}
|
||||
const details = await github.getUser(user.username);
|
||||
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
name: details.name,
|
||||
followers: details.followers,
|
||||
following: details.following,
|
||||
location: details.location,
|
||||
achievements: details.achievements || user.achievements,
|
||||
})
|
||||
.where(eq(users.id, user.id));
|
||||
|
||||
await queue.add("calculateUserPoints", { userId: user.id });
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import db from "@server/db";
|
||||
import github from "@server/lib/github";
|
||||
import queue from "@server/lib/queue";
|
||||
import { repositories, users } from "@server/models";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { FetchRepoDataJobType } from "./fetch-repo-data";
|
||||
|
||||
export type FetchUserRepos = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const fetchUserRepos = async (data: FetchUserRepos) => {
|
||||
const [user] = await db.select().from(users).where(eq(users.id, data.userId));
|
||||
if (!user) {
|
||||
throw new Error("User not found!");
|
||||
}
|
||||
|
||||
const res = await github.getRepositories(user.username, {
|
||||
sort: "stargazers",
|
||||
fetchAll: true,
|
||||
});
|
||||
|
||||
const jobList = [] as FetchRepoDataJobType[];
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
for (const repo of res.repositories) {
|
||||
const data = {
|
||||
...repo,
|
||||
userId: user.id,
|
||||
lastUpdate: repo.lastUpdate.toISOString(),
|
||||
};
|
||||
|
||||
const [existing] = await tx
|
||||
.select({ id: repositories.id })
|
||||
.from(repositories)
|
||||
.where(
|
||||
and(
|
||||
eq(repositories.userId, data.userId),
|
||||
eq(repositories.name, data.name)
|
||||
)
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
await tx
|
||||
.update(repositories)
|
||||
.set(data)
|
||||
.where(eq(repositories.id, existing.id));
|
||||
jobList.push({ id: existing.id, uri: data.uri });
|
||||
} else {
|
||||
const [result] = await tx.insert(repositories).values(data).returning();
|
||||
jobList.push({ id: result.id, uri: data.uri });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Queue fetch repo details
|
||||
queue.addBulk(jobList.map((data) => ({ name: "fetchRepoData", data })));
|
||||
queue.addBulk(
|
||||
jobList.map((data) => ({
|
||||
name: "fetchRepoContributors",
|
||||
data,
|
||||
opts: {
|
||||
attempts: 5,
|
||||
backoff: { type: "exponential", delay: 30000 },
|
||||
jobId: `contributors:${data.uri}`,
|
||||
},
|
||||
}))
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { calculateUserPoints } from "./calculate-user-points";
|
||||
import { fetchRepoContributors } from "./fetch-repo-contributors";
|
||||
import { fetchRepoData } from "./fetch-repo-data";
|
||||
import { fetchUserProfile } from "./fetch-user-profile";
|
||||
import { fetchUserRepos } from "./fetch-user-repos";
|
||||
|
||||
export const jobs = {
|
||||
fetchUserRepos,
|
||||
fetchRepoData,
|
||||
fetchRepoContributors,
|
||||
calculateUserPoints,
|
||||
fetchUserProfile,
|
||||
};
|
||||
|
||||
export type JobNames = keyof typeof jobs;
|
||||
Reference in New Issue
Block a user