feat: initial commit

This commit is contained in:
2024-08-08 06:07:30 +07:00
commit 8f286de718
56 changed files with 6343 additions and 0 deletions
+84
View File
@@ -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));
};
+46
View File
@@ -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 });
};
+26
View File
@@ -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 });
};
+30
View File
@@ -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 });
};
+69
View File
@@ -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}`,
},
}))
);
};
+15
View File
@@ -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;