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

Enhance User Access Token Flow with PR Count Tracking #16

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<<<<<<< HEAD
DATABASE_URL=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
Expand All @@ -12,3 +13,8 @@ MAILER_NAME=
PORT=

SENDGRID_API_KEY=
=======
DATABASE_URL = "Enter your database URL here"
GITHUB_CLIENT_ID = "Enter Github clientId"
GITHUB_CLIENT_SECRET = "Enter Github clientSecret"
>>>>>>> f485fc933e49550f8f297a9d18a1765290d21a97
14 changes: 13 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<<<<<<< HEAD
# Contributing to Club Gamma Backend

Thank you for your interest in contributing to our backend project! This document provides guidelines and steps for setting up the development environment and contributing.
Expand Down Expand Up @@ -187,4 +188,15 @@ npx prisma db push

## License

By contributing, you agree that your contributions will be licensed under the project's license.
By contributing, you agree that your contributions will be licensed under the project's license.
=======
#### Name: Bhavya Prajapati
- Place: Anand, Gujarat
- Bio: Student at Charusat University.
- GitHub: [bhavyaGP](https://github.com/bhavyaGP)

#### Name: Mit Gajera
- Place: Surat, Gujarat, India
- Bio: Student at CHARUSAT
- GitHub: [mitgajera] (https://github.com/mitgajera)
>>>>>>> f485fc933e49550f8f297a9d18a1765290d21a97
44 changes: 18 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ datasource db {
url = env("DATABASE_URL")
}

scalar DateTime

model users {
githubId String @id
name String
Expand All @@ -22,7 +24,11 @@ model users {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
points Int @default(0)
openedPRCount Int @default(0) // Count of opened PRs
closedPRCount Int @default(0) // Count of closed PRs
mergedPRCount Int @default(0) // Count of merged PRs
prs pullRequests[]

}

model pullRequests {
Expand All @@ -42,4 +48,4 @@ model pullRequests {
author users @relation(fields: [authorId], references: [githubId])

@@unique([prNumber, repository])
}
}
35 changes: 32 additions & 3 deletions src/api/auth/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ const jwt = require('jsonwebtoken');
const { default: axios } = require('axios');
const prisma = require("../../utils/PrismaClient");

// Get user info
const getUser = async (req, res, next) => {
try {
const user = req.user;
if (!user) {
logger.warn(`[/auth/getUser] - user not found`);
logger.debug(`[/auth/getUser] - user: ${req.user.sys_id}`);
return next({ path: '/auth/getUser', status: 400, message: "User not found" })
return next({ path: '/auth/getUser', status: 400, message: "User not found" });
}
logger.info(`[/auth/getUser] - success - ${user.sys_id}`);
logger.debug(`[/auth/getUser] - user: ${user.sys_id}`);
Expand All @@ -24,6 +25,7 @@ const getUser = async (req, res, next) => {
}
}

// Logout user
const logout = async (req, res, next) => {
try {
res.clearCookie('token');
Expand All @@ -35,6 +37,7 @@ const logout = async (req, res, next) => {
}
}

// GitHub callback after authentication
const githubCallback = async (req, res, next) => {
const user = req.user;
const token = jwt.sign({ id: user.githubId }, process.env.JWT_SECRET, {
Expand All @@ -44,9 +47,10 @@ const githubCallback = async (req, res, next) => {
logger.info(`[/auth/github/callback] - Successfully authenticated user: ${user.sys_id}`);

// Redirect to frontend
res.redirect(process.env.FRONTEND_URL+"/redirect/"+token);
res.redirect(process.env.FRONTEND_URL + "/redirect/" + token);
}

// Get access token from GitHub
const getAccessToken = async (req, res, next) => {
try {
const code = req.query.code;
Expand Down Expand Up @@ -88,6 +92,7 @@ const getAccessToken = async (req, res, next) => {
}
});

// If the user is not found, create a new one
if (!user || !user.email) {
console.log('User not found, creating new user');
try {
Expand All @@ -110,7 +115,8 @@ const getAccessToken = async (req, res, next) => {
email: primaryEmail,
universityEmail: universityEmail,
avatar: ghUser.avatar_url,
name: ghUser.name?ghUser.name:ghUser.login,
name: ghUser.name ? ghUser.name : ghUser.login,
prCount: 0 // Initialize PR count
},
update: {
email: primaryEmail,
Expand All @@ -124,6 +130,13 @@ const getAccessToken = async (req, res, next) => {
}
}

// Fetch user's PR count and update it in the database
const prCount = await getPullRequestCount(ghUser.login, accessToken); // Use ghUser.login instead of user.githubId
await prisma.users.update({
where: { githubId: user.githubId },
data: { prCount } // Update PR count in the database
});

const token = jwt.sign({ id: user.githubId }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
Expand All @@ -136,6 +149,22 @@ const getAccessToken = async (req, res, next) => {
next({ path: '/auth/getAccessToken', status: 400, message: err.message, extraData: err });
}
};

// Function to fetch pull request count
const getPullRequestCount = async (githubId, accessToken) => {
try {
const response = await axios.get(`https://api.github.com/search/pulls?q=author:${githubId}+is:public`, {
Copy link
Member

Choose a reason for hiding this comment

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

why fetching user's all prs, we don't want his all pr count, we want pr counts of our projects, so you can remove this thing

headers: {
Authorization: `Bearer ${accessToken}`
}
});
return response.data.total_count; // Return the total count of PRs
} catch (error) {
console.error('Error fetching pull request count:', error.response?.data || error.message);
throw new Error('Failed to fetch pull request count from GitHub');
}
};

module.exports = {
getUser,
logout,
Expand Down
14 changes: 10 additions & 4 deletions src/api/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ const passport = require('../../utils/GithubPassportStrategy');

const router = Router();

// Get current user info
router.get('/me', verifyJWT, controller.getUser);

// router.get('/github', passport.authenticate('github', { scope: ['user:email'] }));
// router.get('/github/callback', passport.authenticate('github', { failureRedirect: process.env.FRONTEND_URL }), controller.githubCallback);
// router.post('/logout', controller.logout); // Add logout route
router.get('/access_token', controller.getAccessToken); // Add access token route
// GitHub authentication routes
router.get('/github', passport.authenticate('github', { scope: ['user:email'] }));
Copy link
Member

Choose a reason for hiding this comment

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

comment these routes

router.get('/github/callback', passport.authenticate('github', { failureRedirect: process.env.FRONTEND_URL }), controller.githubCallback);

// Logout route
router.post('/logout', controller.logout);

// Access token route
router.get('/access_token', controller.getAccessToken);

module.exports = router;
51 changes: 22 additions & 29 deletions src/api/leaderboard/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@ const prisma = require("../../utils/PrismaClient");

const formatUsers = (users, allUsers) => {
return users.map((user) => {
// Calculate PR statistics
const prStats = user.prs.reduce(
(acc, pr) => {
switch (pr.state.toLowerCase()) {
case "open":
acc.opened++;
break;
case "closed":
acc.closed++;
break;
case "merged":
acc.merged++;
break;
}
return acc;
},
{ opened: 0, closed: 0, merged: 0 }
(acc, pr) => {
switch (pr.state.toLowerCase()) {
case "open":
acc.opened++;
break;
case "closed":
acc.closed++;
break;
case "merged":
acc.merged++;
break;
}
return acc;
},
{ opened: 0, closed: 0, merged: 0 }
);

// Calculate user's rank
const rank = allUsers.findIndex((u) => u.points <= user.points) + 1;

// Format the user
return {
name: user.name,
githubId: user.githubId,
Expand All @@ -41,7 +38,7 @@ const formatUsers = (users, allUsers) => {
}

const filterByUsers = async (req, res) => {
const {minPoints, maxPoints, minPrs, page = 1, limit = 10, name} = req.query;
const { minPoints, maxPoints, minPrs, page = 1, limit = 10, name } = req.query;

const userQueryArguments = {
select: {
Expand Down Expand Up @@ -73,29 +70,29 @@ const filterByUsers = async (req, res) => {
OR: [
{
name: {
contains: name, // Changed from startsWith to contains
contains: name,
mode: "insensitive",
},
},
{
githubId: {
contains: name, // Changed from startsWith to contains
contains: name,
mode: "insensitive",
},
},
],
};
}

if (minPoints || maxPoints) {
userQueryArguments.where = userQueryArguments.where || {};
userQueryArguments.where.points = {
gte: parseInt(minPoints) || 0, // Filter users with at least minPoints (default 0)
lte: parseInt(maxPoints) || undefined, // Filter users with at most maxPoints
gte: parseInt(minPoints) || 0,
lte: parseInt(maxPoints) || undefined,
};
}

try {
// First get all users ordered by points to calculate rank
const allUsers = await prisma.users.findMany({
orderBy: {
points: "desc",
Expand All @@ -106,12 +103,9 @@ const filterByUsers = async (req, res) => {
},
});

// Find all matching users with all required fields
let users = await prisma.users.findMany(userQueryArguments);
let totalUsersWithFilter = users.length;

// Prisma ORM doesn't support filtering directly by the aggregated count in the findMany function
// So we filter in application
if (minPrs) {
let _countLower = 0;
users = users.filter(item => {
Expand All @@ -121,10 +115,9 @@ const filterByUsers = async (req, res) => {
_countLower++;
return false;
});
totalUsersWithFilter-=_countLower;
totalUsersWithFilter -= _countLower;
}

// Javascript Pagination
const skip = (parseInt(page) - 1) * parseInt(limit);
const take = parseInt(limit);

Expand Down
2 changes: 2 additions & 0 deletions src/api/leaderboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const { verifyJWT } = require('../../utils/Middlewares');

const router = Router();

router.use(verifyJWT);

router.get('/', controller.filterByUsers);
router.get('/search', controller.filterByUsers);
router.get('/filter', controller.filterByUsers);
Expand Down
Loading