Skip to content
Open
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
68 changes: 68 additions & 0 deletions client/src/infra/rest/apis/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// api/github/index.ts
import { patch, post } from '../..';
import {
AnalyzeUserPayload,
AnalyzeUserResponse,
GetIssueMetricsPayload,
IssueMetricsResponse,
GetPRMetricsPayload,
PRMetricsResponse,
GetLanguagesPayload,
LanguagesResponse,
GetFilesPayload,
GetUserReposPayload,
UserReposResponse,
} from './typing';
import { ApiResponse, BaseApiResponse } from '../../typings';

export const analyzeUser = async (payload: AnalyzeUserPayload) => {
return post<
AnalyzeUserPayload,
ApiResponse<AnalyzeUserResponse>
>('/api/github/analyze-user', true, payload);
};

export const getIssueMetrics = async (payload: GetIssueMetricsPayload) => {
return post<
GetIssueMetricsPayload,
ApiResponse<IssueMetricsResponse>
>('/api/github/issue-metrics', true, payload);
};

export const getPRMetrics = async (payload: GetPRMetricsPayload) => {
return post<
GetPRMetricsPayload,
ApiResponse<PRMetricsResponse>
>('/api/github/pr-metrics', true, payload);
};

export const getLanguages = async (payload: GetLanguagesPayload) => {
return post<
GetLanguagesPayload,
ApiResponse<LanguagesResponse>
>('/api/github/languages', true, payload);
};

export const getFiles = async (payload: GetFilesPayload) => {
return post<
GetFilesPayload,
ApiResponse<any[]>
>('/api/github/files', true, payload);
};

export const getUserRepos = async (payload: GetUserReposPayload) => {
return post<
GetUserReposPayload,
ApiResponse<UserReposResponse>
>('/api/github/user-repos', true, payload);
};

export const detectCapabilities = async (payload: {
files: any[];
deps?: string[];
}) => {
return post<
{ files: any[]; deps?: string[] },
ApiResponse<string[]>
>('/api/github/detect-capabilities', true, payload);
};
60 changes: 60 additions & 0 deletions client/src/infra/rest/apis/github/typing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export interface AnalyzeUserPayload{
username: string;
}
export interface AnalyzeUserResponse{
repos: Array<{
repo:string;
languages: string[];
capabilities: string[];
avgPRMergeTime: number | null;
avgIssueResolutionTime: number | null;
} >;
}
export interface GetIssueMetricsPayload{
owner: string;
repo: string;
}

export interface IssueMetricsResponse{
issue: Array<{
issue: number;
resolutionHours: number;
}>;
}

export interface GetPRMetricsPayload{
owner : string;
repo: string ;
}

export interface PRMetricsResponse{
prs:Array<{
prnumber:number;
mergetimehours:number;
}>;
}

export interface GetLanguagesPayload{
owner:string;
repo:string;
}

export interface LanguagesResponse {
[language: string]: number;
}

export interface GetFilesPayload {
owner: string;
repo: string;
}

export interface GetUserReposPayload {
username: string;
}

export interface UserReposResponse {
repos: Array<{
owner: { login: string };
name: string;
}>;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Import this in navbar to make it workable.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useEffect, useState } from "react";

export default function Recommendation() {
const [result, setResult] = useState("");
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchRecommendation = async () => {
try {
const res = await fetch("http://localhost:5000/api/recommend", {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use the created apis function instead of using fetch.

method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ platform: "facebook" })
});

const data = await res.json();
setResult(data.recommendation);
} catch (err) {
setResult("Error getting recommendation");
} finally {
setLoading(false);
}
};

fetchRecommendation();
}, []);

return (
<div>
{loading && <p>Generating AI recommendation...</p>}
{!loading && <pre>{result}</pre>}
</div>
);
}
38 changes: 38 additions & 0 deletions server/gemini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { GEMINI_API_KEY } from "./src/config/env.js";

export async function getAIRecommendation(analysisData) {
const prompt = `
You are an AI recommendation system.
Based on the following analysis of repos of github, generate clear recommendations of what tech stack should be learned to improve skills according to tech stack and also tell some future project ideas that can be built using the current skills and the recommended skills.
Analysis:
${JSON.stringify(analysisData, null, 2)}
`;

const response = await fetch(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use axios instead of fetch

`https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}`,


{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [
{
parts: [{ text: prompt }]
}
]
})
}
);

const data = await response.json();

console.log("Raw Gemini response:", JSON.stringify(data, null, 2));
if (!data.candidates || data.candidates.length === 0) {
throw new Error("Gemini returned no response");
}

return data.candidates[0].content.parts[0].text;

}
17 changes: 17 additions & 0 deletions server/index2.js
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the usecase of this file?
If not required, remove it.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "dotenv/config";

import { analyzeUser } from "./src/services/index.js";
import { getAIRecommendation } from "./gemini.js";

async function run() {
try {
const analysis = await analyzeUser("facebook");
await getAIRecommendation({ test: "hello" });
const aiResult = await getAIRecommendation(analysis);
console.log(aiResult);
} catch (err) {
console.error(err);
}
}

run();
2 changes: 1 addition & 1 deletion server/package-lock.json

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

2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.4.7",
"dotenv": "^16.6.1",
"express": "^4.21.2",
"helmet": "^8.1.0",
"hpp": "^0.2.3",
Expand Down
3 changes: 3 additions & 0 deletions server/src/config/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ export const CLOUDINARY_API_SECRET =
export const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'dev.admin@example.com';
export const RESEND_API_KEY =
process.env.RESEND_API_KEY || 'dev_resend_key_abc123';

// AI Configuration
export const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add some optional OR value.
process.env.GEMINI_API_KEY || 'gemini_api_key'

94 changes: 94 additions & 0 deletions server/src/services/analyze-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Analyze User - Comprehensive GitHub user analysis
* Gathers repositories, analyzes code patterns, and computes metrics
*/

import { fetchGitHubRepos } from './fetch-github-repos.js';
import { fetchRepositoryFiles } from './fetch-repository-files.js';
import { fetchRepositoryLanguages } from './fetch-repository-languages.js';
import { fetchPullRequestMetrics } from './fetch-pull-request-metrics.js';
import { fetchIssueMetrics } from './fetch-issue-metrics.js';
import { detectCapabilities } from './detect-capabilities.js';
Copy link
Collaborator

Choose a reason for hiding this comment

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

All these files are not required. Try to collab them into a single file.
And move the github logic part in controllers. No need to create a separate dir of services.


/**
* Analyzes a GitHub user's repositories and generates capability metrics
* Fetches repo information, analyzes files, and computes PR/issue metrics
* @param {string} username - GitHub username to analyze
* @returns {Promise<Array<Object>>} Analysis results per repository
* @throws {Error} If user not found or API fails
*/
export async function analyzeUser(username) {
if (!username || typeof username !== 'string') {
throw new Error('Invalid username: must be a non-empty string');
}

try {
const repos = await fetchGitHubRepos(username);
const analysisResults = [];

for (const repo of repos) {
console.log('Analyzing repo:', repo.owner.login, repo.name);

try {
// Fetch repository files
console.log('Fetching files...');
const files = await fetchRepositoryFiles(
repo.owner.login,
repo.name
);

// Detect capabilities based on files
const capabilities = detectCapabilities(files, []);

// Fetch PR metrics
console.log('Fetching PR metrics...');
const prMetrics = await fetchPullRequestMetrics(
repo.owner.login,
repo.name
);

// Fetch issue metrics
console.log('Fetching issue metrics...');
const issueMetrics = await fetchIssueMetrics(
repo.owner.login,
repo.name
);

// Fetch languages
console.log('Fetching languages...');
const languages = await fetchRepositoryLanguages(repo);

// Calculate averages
const avgPRMergeTime =
prMetrics.length
? prMetrics.reduce((a, b) => a + b.mergeTimeHours, 0) /
prMetrics.length
: null;

const avgIssueResolutionTime =
issueMetrics.length
? issueMetrics.reduce((a, b) => a + b.resolutionTimeHours, 0) /
issueMetrics.length
: null;

analysisResults.push({
repo: repo.name,
languages: Object.keys(languages),
capabilities,
avgPRMergeTime,
avgIssueResolutionTime
});
} catch (repoError) {
console.error(`Error analyzing repo ${repo.name}:`, repoError.message);
// Continue with next repo even if one fails
continue;
}
}

return analysisResults;
} catch (error) {
throw new Error(
`Failed to analyze user ${username}: ${error.message}`
);
}
}
Loading
Loading