Skip to content
Merged
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
2 changes: 2 additions & 0 deletions nextstep-backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import validateUser from "./middleware/validateUser";
import loadOpenApiFile from "./openapi/openapi_loader";
import resource_routes from './routes/resources_routes';
import resume_routes from './routes/resume_routes';
import githubRoutes from './routes/github_routes';

const specs = swaggerJsdoc(options);

Expand Down Expand Up @@ -74,5 +75,6 @@ app.use('/user', usersRoutes);
app.use('/resource', resource_routes);
app.use('/room', roomsRoutes);
app.use('/resume', resume_routes);
app.use('/github', githubRoutes);

export { app, corsOptions };
93 changes: 93 additions & 0 deletions nextstep-backend/src/controllers/github_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import axios from 'axios';
import { Request, Response } from 'express';

const clientId = process.env.GITHUB_CLIENT_ID;
const clientSecret = process.env.GITHUB_CLIENT_SECRET;

export const handleGitHubOAuth = async (req: Request, res: Response) => {
const { code } = req.body;

if (!code) {
return res.status(400).json({ error: 'Authorization code is required' });
}

try {
const tokenResponse = await axios.post('https://github.com/login/oauth/access_token', {
headers: { 'Content-Type': 'application/json' },
client_id: clientId,
client_secret: clientSecret,
code: code,
}) as any;

const tokenData = await tokenResponse.data;
const params = new URLSearchParams(tokenData);
const accessToken = params.get('access_token');

if (!accessToken) {
return res.status(400).json({ error: 'Failed to retrieve access token' });
}

const userResponse = await axios.get('https://api.github.com/user', {
headers: { Authorization: `Bearer ${accessToken}` },
}) as {status: number, data: { login?: string }, json: () => Promise<any>};

if (userResponse.status !== 200) {
const errorText = await userResponse.data;
return res.status(400).json({ error: errorText });
}

const userData = await userResponse.data;
res.json({ username: userData.login });
} catch (error) {
console.error('Error during GitHub OAuth:', error);
res.status(500).json({ error: 'Internal server error' });
}
};

export const fetchGitHubRepos = async (req: Request, res: Response) => {
const { username } = req.params;
const { accessToken } = req.query; // Optional access token for authenticated requests

try {
const apiUrl = `https://api.github.com/users/${username}/repos`;

const headers = accessToken
? { Authorization: `Bearer ${accessToken}` }
: undefined;

const response = await axios.get(apiUrl, { headers }) as { data: any, status: number };

if (response.status !== 200) {
return res.status(400).json({ error: `Error fetching repos: ${response.status}` });
}

const repos = await response.data;
res.json(repos);
} catch (error) {
console.error('Error fetching repos:', error);
res.status(500).json({ error: 'Internal server error' });
}
};

export const fetchRepoLanguages = async (req: Request, res: Response) => {
const { repoUrl } = req.query;

if (!repoUrl) {
return res.status(400).json({ error: 'Repository URL is required' });
}

try {
// Convert the repoUrl to the GitHub API URL if necessary
const apiUrl = (repoUrl as string).replace('https://github.com/', 'https://api.github.com/repos/');
const response = await axios.get(`${apiUrl}/languages`);

if (response.status !== 200) {
return res.status(400).json({ error: `Error fetching languages: ${response.statusText}` });
}

res.json(response.data);
} catch (error) {
console.error('Error fetching languages:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
10 changes: 10 additions & 0 deletions nextstep-backend/src/routes/github_routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import express from 'express';
import { handleGitHubOAuth, fetchGitHubRepos, fetchRepoLanguages } from '../controllers/github_controller';

const router = express.Router();

router.post('/oauth', handleGitHubOAuth);
router.get('/repos/:username', fetchGitHubRepos);
router.get('/languages', fetchRepoLanguages);

export default router;
2 changes: 2 additions & 0 deletions nextstep-frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Chat from './pages/Chat';
import Resume from './pages/Resume';
import TopBar from './components/TopBar';
import Layout from './components/Layout';
import MainDashboard from './pages/MainDashboard';

const App: React.FC = () => {
return (
Expand All @@ -28,6 +29,7 @@ const App: React.FC = () => {
<Route path="/post/:postId" element={<RequireAuth><TopBar /><Layout className="post-details"><PostDetails /></Layout></RequireAuth>} />
<Route path="/chat" element={<RequireAuth><TopBar /><Layout className="chat"><Chat /></Layout></RequireAuth>} />
<Route path="/resume" element={<RequireAuth><TopBar /><Layout className="resume"><Resume /></Layout></RequireAuth>} />
<Route path="/main-dashboard" element={<RequireAuth><TopBar /><Layout><MainDashboard /></Layout></RequireAuth>} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</Router>
Expand Down
2 changes: 1 addition & 1 deletion nextstep-frontend/src/components/Footer.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.footer {
background-color: var(--color-5);
background-color: #233752;
color: var(--color-2);
padding: 10px 20px;
text-align: center;
Expand Down
4 changes: 2 additions & 2 deletions nextstep-frontend/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const TopBar: React.FC = () => {
};

return (
<AppBar position="relative" sx={{ width: '100vw', left: 0 }} className='top-bar'>
<AppBar position="relative" sx={{ width: '100vw', left: 0, backgroundColor: '#233752' }} className='top-bar'>
<Toolbar>
<Tooltip title="Home">
<IconButton color="inherit" onClick={() => navigate('/dashboard')} sx={{ mx: 1 }}>
<IconButton color="inherit" onClick={() => navigate('/main-dashboard')} sx={{ mx: 1 }}>
<Home fontSize='large'/>
</IconButton>
</Tooltip>
Expand Down
72 changes: 72 additions & 0 deletions nextstep-frontend/src/handlers/githubAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import api from "../serverApi.ts";

// Function to fetch GitHub repositories by username
export const fetchGitHubRepos = async (username: string) => {
try {
const response = await api.get(`github/repos/${username}`) as any;
if (response.status !== 200) {
throw new Error(`Error fetching repos: ${response.statusText}`);
}
return response.data;
} catch (error) {
console.error(error);
return [];
}
};

// Function to connect to GitHub by username
export const connectToGitHub = async (username: string) => {
if (!username) {
throw new Error('Username is required to connect to GitHub.');
}
return await fetchGitHubRepos(username);
};

// Function to initiate GitHub OAuth login (redirect only)
export const initiateGitHubOAuth = () => {
const clientId = import.meta.env.VITE_GITHUB_CLIENT_ID; // Use environment variable
const redirectUri = import.meta.env.VITE_GITHUB_REDIRECT_URI; // Use environment variable
const scope = 'public_repo'; // Only read access to repositories
const authUrl = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}`;
window.location.href = authUrl; // Redirect the user to GitHub's authorization page
};

// Function to handle GitHub OAuth token exchange and fetching user details
export const handleGitHubOAuth = async (code: string) => {
try {
const response = await api.post('github/oauth', {
headers: { 'Content-Type': 'application/json' },
code: code,
}) as {data: {username?: string, error?: string}, status: number};

if (response.status !== 200) {
const errorText = await response.data.error;
console.error('Backend OAuth Error:', errorText);
throw new Error('Failed to authenticate with GitHub');
}
else if (!response.data.username) {
console.error('Backend OAuth Error:', response.data.error);
throw new Error('Failed to retrieve user information from GitHub');
}

return response.data.username;
} catch (error) {
const err : Error = error as Error;
console.error('Error during GitHub OAuth:', error);
throw new Error('Internal server error: ' + err.message);
}
};

// Function to fetch languages for a specific repository
export const fetchRepoLanguages = async (repoUrl: string) => {
try {
const response = await api.get(`github/languages?repoUrl=${encodeURIComponent(repoUrl)}`) as any;
if (response.status !== 200) {
throw new Error(`Error fetching languages: ${response.statusText}`);
}
return await response.data;
} catch (error) {
console.error(error);
return {};
}
};
4 changes: 2 additions & 2 deletions nextstep-frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Login: React.FC = () => {

setUserAuth(res.data);

navigate("/dashboard");
navigate("/main-dashboard");
} catch (error) {
console.error("Google login failed:", error);
setError("Google login failed.");
Expand All @@ -51,7 +51,7 @@ const Login: React.FC = () => {
// Handle successful login, e.g., save tokens, redirect, etc.
setUserAuth(response.data)

navigate('/dashboard'); // Redirect to dashboard or another page after login
navigate('/main-dashboard'); // Redirect to dashboard or another page after login

} catch (error) {
// Handle login error
Expand Down
Loading
Loading