Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
797197a
refined refresh token logic
yb175 Nov 12, 2025
ba20286
started
Nov 12, 2025
8e8ae14
ratelimiter
Pratham-9365 Nov 14, 2025
7880941
Merge pull request #6 from yb175/pr-5
Pratham-9365 Nov 14, 2025
98ea598
Merge branch 'main' of https://github.com/yb175/pullShark
Nov 15, 2025
93f35bd
badmash
Nov 15, 2025
5087f53
badmashi
Nov 15, 2025
1c10b09
Merge branch 'main' of https://github.com/yb175/pullShark
Nov 15, 2025
1647471
demm
Nov 15, 2025
b8ee7b7
initial
Nov 15, 2025
501a8a7
enhanced
Nov 20, 2025
76a5d2e
repos and prs fetched
Pratham-9365 Nov 22, 2025
7a8ed10
Merge branch 'main' of https://github.com/yb175/pullShark
Pratham-9365 Nov 22, 2025
5c6a35f
Issue 9 resolved do not merge I would test it
yb175 Nov 22, 2025
830fde8
CORS ISSUES FIXED
yb175 Nov 22, 2025
98eb8f8
Cors issue fixed
yb175 Nov 22, 2025
3beea93
login page added
Nov 22, 2025
abe8d9f
github call back uri changed
yb175 Nov 22, 2025
9eb6235
logic error fixed
Nov 22, 2025
9a91523
Merge pull request #12 from yb175/vansh
vanshkhurana05 Nov 22, 2025
c951b90
repo page added
Nov 22, 2025
3896f59
Merge branch 'vansh'
Nov 22, 2025
2c0cf2b
cors added
Pratham-9365 Nov 23, 2025
60bc9b2
sfduivh
Pratham-9365 Nov 23, 2025
521774d
idk
Pratham-9365 Nov 23, 2025
f81e086
Merge branch 'main' of https://github.com/Pratham-9365/pullShark into…
Pratham-9365 Nov 23, 2025
5d2d403
Merge pull request #13 from Pratham-9365/pratham
Pratham-9365 Nov 23, 2025
3b78ece
fixed
Nov 23, 2025
5a2488a
Auth logic fixed
yb175 Nov 23, 2025
2dfd936
Merge branch 'main' of https://github.com/yb175/pullShark
yb175 Nov 23, 2025
e6d2db7
callback issue fixed
yb175 Nov 23, 2025
e5578fb
repo
Nov 23, 2025
2d268c7
Merge pull request #14 from yb175/vansh
vanshkhurana05 Nov 23, 2025
c5cd48c
The login issue in frontend is fixed
yb175 Nov 23, 2025
21cd0dd
Merge branch 'main' of https://github.com/yb175/pullShark
yb175 Nov 23, 2025
0461055
pagination added
Pratham-9365 Nov 24, 2025
568d530
Merge pull request #15 from Pratham-9365/pratham
Pratham-9365 Nov 24, 2025
1c23f51
prs are fetched and ready for analysis in compressed form
yb175 Nov 25, 2025
d424038
Changes made
yb175 Nov 27, 2025
29df7b6
user have to login again and again issue resolved
yb175 Nov 27, 2025
051960e
Integrated backend and llm
yb175 Nov 28, 2025
0ab6416
user have to login again and again issue resolved
yb175 Nov 27, 2025
3c99acb
Merge branch 'main' of https://github.com/yb175/pullShark
yb175 Nov 28, 2025
8295133
webhook added
Pratham-9365 Nov 28, 2025
980c6e6
mailer
Pratham-9365 Nov 29, 2025
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
7 changes: 6 additions & 1 deletion backend/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URL=
JWT_SECRET_KEY=
JWT_REFRESH_SECRET_KEY=
REDIS_URL=
REDIS_PASSWORD=
REDIS_HOST=
REDIS_PORT=
GITHUB_WEBHOOK_SECRET=
EMAIL_USER=
EMAIL_PASS=
8 changes: 6 additions & 2 deletions backend/config/redisClient.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { createClient } from 'redis';

const redisClient = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
username: 'default',
password: process.env.REDIS_PASSWORD,
socket: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
}
});

redisClient.on('error', (err) => console.error('Redis Client Error', err));

await redisClient.connect();

export default redisClient;
9 changes: 9 additions & 0 deletions backend/config/redisConnect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import redisClient from "./redisClient.js";

async function redisConnect() {
redisClient.on('error', err => console.log('Redis Client Error', err));
console.log('Redis client connected');
await redisClient.connect();
}

export default redisConnect ;
4 changes: 4 additions & 0 deletions backend/controllers/auth/callback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default function callback(req, res) {
const code = req.query.code;
res.redirect(`http://localhost:5173/login?code=${code}`);
}
64 changes: 36 additions & 28 deletions backend/controllers/auth/exchangecode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import jwt from "jsonwebtoken";
import axios from "axios";
import UserModel from "../../models/user/userSchema.js";
import UserModel from "../../models/user/userSchema.js";

export default async function exchangeToken(req, res) {
const code = req.params.code;
Expand All @@ -23,7 +22,21 @@ export default async function exchangeToken(req, res) {
{ headers: { Accept: "application/json" } }
);

const access_token = tokenResponse.data.access_token;
const {
access_token,
refresh_token,
refresh_token_expires_in,
expires_in,
} = tokenResponse.data;

const ghAccessTokenExpiresAt = expires_in
? new Date(Date.now() + expires_in * 1000)
: new Date(Date.now() + 2 * 60 * 60 * 1000);

const ghRefreshTokenExpiresAt = refresh_token_expires_in
? new Date(Date.now() + refresh_token_expires_in * 1000)
: new Date(Date.now() + 180 * 24 * 60 * 60 * 1000);

if (!access_token) {
return res.status(400).json({
success: false,
Expand All @@ -37,59 +50,54 @@ export default async function exchangeToken(req, res) {

const ghUser = userResponse.data;


// Encrypting the GitHub token using jwt before sorting
const encryptedAccessToken = jwt.sign(
{ access_token },
process.env.JWT_SECRET_KEY,
{ expiresIn: "7d" }
);

let user = await UserModel.findOne({ userId: ghUser.id });
if (!user) {
user = new UserModel({
userId: ghUser.id,
email: ghUser.email || `${ghUser.login}@users.noreply.github.com`,
username: ghUser.login,
accessToken: encryptedAccessToken,
avatarUrl: ghUser.avatar_url,
accessToken: access_token,
accessTokenExpiresAt: ghAccessTokenExpiresAt,
refreshToken: refresh_token,
refreshTokenExpiresAt: ghRefreshTokenExpiresAt,
});
} else {
user.accessToken = encryptedAccessToken;
user.accessToken = access_token;
user.accessTokenExpiresAt = ghAccessTokenExpiresAt;
user.refreshToken = refresh_token;
user.refreshTokenExpiresAt = ghRefreshTokenExpiresAt;
user.avatarUrl = ghUser.avatar_url;
}

// Generate access and refresh tokens
const accessToken = jwt.sign(
{
id: user.userId,
username: user.username,
email: user.email,
avatarUrl: user.avatarUrl,
ghTokenExpiresAt: ghAccessTokenExpiresAt,
ghAccessToken: access_token,
},
process.env.JWT_SECRET_KEY,
{ expiresIn: "2hr" } // access token vaise 1hr ya usse kam rkhna chiye
{ expiresIn: "4d" }
);

const refreshToken = jwt.sign(
{
id: user.userId,
username: user.username,
email: user.email,
},
process.env.JWT_REFRESH_SECRET_KEY,
{ expiresIn: "7d" } // refresh token abhi k liye 7 din bad expire hoga
);

user.refreshToken = refreshToken;
await user.save();

res.cookie("accesstoken", accessToken, { httpOnly: true, maxAge: 2 * 60 * 60 * 1000 });
res.cookie("refreshToken", refreshToken, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000 });
res.cookie("accesstoken", accessToken, {
httpOnly: true,
maxAge: 2 * 60 * 60 * 1000,
});
req.user = user ;
res.status(201).json({
success: true,
message: "User logged in successfully",
data: {
username: user.username,
email: user.email,
userId: user.userId,
avatarUrl: user.avatarUrl,
},
});
} catch (err) {
Expand Down
35 changes: 35 additions & 0 deletions backend/controllers/auth/getRepoPullRequests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import getDecryptedGithubToken from "../../utils/decryptGithubToken.js";
import axios from "axios";

export default async function getRepoPullRequests(req, res) {
try {
// Use authenticated user
const user = req.user;
if (!user || !user.userId) return res.status(401).json({ success: false, message: "User not authenticated" });
const accesstoken = req.cookies.accesstoken;
if (!accesstoken) return res.status(401).json({ success: false, message: "GitHub token not found" });
const payload = await getDecryptedGithubToken(accesstoken);
// console.log(payload);
if (!payload) return res.status(401).json({ success: false, message: "GitHub data not found" });

// Repo name comes from the URL; owner is the authenticated user's GitHub username
const repo = req.params.repo;
// console.log(repo);

const owner = user.username;
if (!repo) return res.status(400).json({ success: false, message: "Missing repo name" });
//console.log(payload.ghAccessToken);

const per_page = (req.pagination && req.pagination.limit) ? req.pagination.limit : 5;
const page = (req.pagination && req.pagination.page) ? req.pagination.page : 1;

const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/pulls`, {
headers: { Authorization: `token ${payload.ghAccessToken}` },
params: { state: "open", per_page, page }
});
res.json({ success: true, pulls: response.data, pagination: req.pagination || { limit: per_page, page } });
} catch (err) {
console.error("getRepoPullRequests error:", err.message);
res.status(500).json({ success: false, message: err.message });
}
}
59 changes: 59 additions & 0 deletions backend/controllers/auth/getUserRepos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import getDecryptedGithubToken from "../../utils/decryptGithubToken.js";
import axios from "axios";

export default async function getUserRepos(req, res) {
try {
const user = req.user;
if (!user || !user.userId) {
return res.status(401).json({ success: false, message: "User not authenticated" });
}

const accessToken = req.cookies.accesstoken;
if (!accessToken) {
return res.status(401).json({ success: false, message: "GitHub token not found" });
}

const payload = await getDecryptedGithubToken(accessToken);

// Use pagination middleware values if present, otherwise fall back
const per_page = (req.pagination && req.pagination.limit) ? req.pagination.limit : 5;
const page = (req.pagination && req.pagination.page) ? req.pagination.page : 1;

const response = await axios.get("https://api.github.com/user/repos", {
headers: { Authorization: `token ${payload.ghAccessToken}` },
params: { page, per_page, sort: "updated" }
});

const repos = response.data || [];

const filtered = repos.map(repo => ({
id: repo.id,
name: repo.name,
full_name: repo.full_name,
private: repo.private,
url: repo.url,

owner: repo.owner ? {
login: repo.owner.login,
id: repo.owner.id,
avatar_url: repo.owner.avatar_url,
html_url: repo.owner.html_url,
type: repo.owner.type
} : null,

html_url: repo.html_url,
description: repo.description,
forks_count: repo.forks_count,
stargazers_count: repo.stargazers_count,
watchers_count: repo.watchers_count,
open_issues_count: repo.open_issues_count,
language: repo.language
}));

return res.json({ success: true, repos: filtered, pagination: req.pagination || { limit: per_page, page } });

} catch (err) {
console.error("getUserRepos error:", err.message);
return res.status(500).json({ success: false, message: err.message });
}
}
13 changes: 12 additions & 1 deletion backend/controllers/auth/logoutuser.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,18 @@ export default async function logoutUser(req, res) {
}
}

const userId = req.user?.id || req.body.userId;

// Extract userId from access token payload (verify signature)
let userId;
if (accessToken) {
try {
const payload = jwt.verify(accessToken, process.env.JWT_SECRET_KEY);
userId = payload.id;
console.log("userid found: " + userId);
} catch (e) {
throw new error ("userid couldnt be found" + e);
}
}
if (userId) {
await UserModel.updateOne({ userId }, { $unset: { refreshToken: "" } });
}
Expand Down
6 changes: 3 additions & 3 deletions backend/controllers/auth/redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* // User visits endpoint triggering this function
* // Gets redirected to: https://github.com/login/oauth/authorize?client_id=123&redirect_uri=https://app.com/callback&scope=read:user%20user:email%20repo
*/
export default function redirectUser(req,res){
const redirectURL = `https://github.com/login/oauth/authorize?client_id=${process.env.GITHUB_CLIENT_ID}&redirect_uri=${process.env.GITHUB_CALLBACK_URL}&scope=read:user%20user:email%20repo`
res.redirect(redirectURL) ;
export default function redirectUser(req, res) {
const redirectURL = `https://github.com/login/oauth/authorize?client_id=${process.env.GITHUB_CLIENT_ID}&redirect_uri=http://localhost:3000/auth/callback&scope=read:user%20user:email%20repo`
res.redirect(redirectURL);
}
31 changes: 0 additions & 31 deletions backend/controllers/auth/refreshToken.js

This file was deleted.

Loading