Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6e2bc42
leetcode leaderboard integration, routing, pages, and navbar
iOliver678 Mar 8, 2025
f0809b4
Added leetcode leaderboard to projects section
iOliver678 Mar 9, 2025
2be52fe
Added Url params to communicate with frontend, fixed routing issue
iOliver678 Mar 9, 2025
df85505
leetcode leaderboard integration, routing, pages, and navbar
iOliver678 Mar 8, 2025
d1dd805
Added leetcode leaderboard to projects section
iOliver678 Mar 9, 2025
a870802
Added Url params to communicate with frontend, fixed routing issue
iOliver678 Mar 9, 2025
25d9796
Merge branch 'leetcode-leaderboard' of https://github.com/SCE-Develop…
adarshm11 Jul 31, 2025
c96bac6
rebased with dev
adarshm11 Jul 31, 2025
8b0f59c
lint bruh
adarshm11 Jul 31, 2025
22ae34f
updated leetcode leaderboard route
adarshm11 Jul 31, 2025
5f91e38
Merge branch 'dev' of https://github.com/SCE-Development/Clark into l…
adarshm11 Aug 1, 2025
2739956
migrated led matrix frontend pages fully
adarshm11 Aug 15, 2025
b97119c
fixed misnamed route
adarshm11 Aug 15, 2025
fec4baf
created dummy endpoint + util functions for led matrix
adarshm11 Aug 16, 2025
59fa168
created apifunctions for led matrix
adarshm11 Aug 16, 2025
ea46a1a
continued dev of led matrix frontend - still need fix of spacing for …
adarshm11 Aug 16, 2025
22983bf
im gonna kill lint
adarshm11 Aug 16, 2025
a0fd1e8
styling fixes
adarshm11 Aug 16, 2025
6d7f514
created framework for util functions
adarshm11 Aug 17, 2025
6780ab2
added starting logic for querying all users
adarshm11 Aug 18, 2025
e457756
removed hardcoded test users
adarshm11 Aug 18, 2025
bf5c08e
minor changes
adarshm11 Aug 18, 2025
68cdd29
leetcode leaderboard clark page is done
adarshm11 Aug 29, 2025
0a73b52
lint...
adarshm11 Aug 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
140 changes: 140 additions & 0 deletions api/main_endpoints/routes/LeetCodeLeaderboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const express = require('express');
const router = express.Router();
const {
OK,
SERVER_ERROR,
UNAUTHORIZED,
FORBIDDEN,
BAD_REQUEST
} = require('../../util/constants.js').STATUS_CODES;
const {
decodeToken,
checkIfTokenSent
} = require('../util/token-functions.js');
const logger = require('../../util/logger.js');
const {
healthCheck,
getAllUsers,
addUserToLeaderboard,
deleteUserFromLeaderboard,
checkIfUserExists,
} = require('../util/LeetCodeLeaderboard.js');
const AuditLogActions = require('../util/auditLogActions.js');
const AuditLog = require('../models/AuditLog.js');

const runningInDevelopment = process.env.NODE_ENV !== 'production'
&& process.env.NODE_ENV !== 'test';

router.get('/healthCheck', async (_, res) => {
if (runningInDevelopment) {
return res.sendStatus(OK);
}
const dataFromSign = healthCheck();
if(!dataFromSign) {
return res.sendStatus(SERVER_ERROR);
}
return res.status(OK).json(dataFromSign);
});

router.get('/getAllUsers', async (req, res) => {
if (!checkIfTokenSent(req)) {
return res.status(FORBIDDEN).send('Missing API token');
}
if (!decodeToken(req)) {
return res.status(UNAUTHORIZED).send('Invalid API token');
}

const users = await getAllUsers();
if (!users) {
return res.status(SERVER_ERROR);
}
return res.status(OK).send({
users,
});
});

router.post('/addUser', async (req, res) => {
if (!checkIfTokenSent(req)) {
return res.status(FORBIDDEN).send('Missing API token');
}
const decoded = decodeToken(req);
if (!decoded) {
return res.status(UNAUTHORIZED).send('Invalid API token');
}

const { username, firstName, lastName } = req.body;
const required = [
{ value: username, title: 'LeetCode username', },
{ value: firstName, title: 'User\'s first name', },
{ value: lastName, title: 'User\'s last name', }
];

const missingValue = required.find(({ value }) => !value);

if (missingValue) {
return res.status(BAD_REQUEST).send(`${missingValue.title} missing from request`);
}

if (!await addUserToLeaderboard({
username,
firstName,
lastName
})) {
return res.sendStatus(SERVER_ERROR);
}

AuditLog.create({
userId: decoded._id,
action: AuditLogActions.UPDATE_SIGN, // ADD_LEETCODE_USER
details: { username },
});
return res.sendStatus(OK);
});

router.post('/deleteUser', async (req, res) => {
if (!checkIfTokenSent(req)) {
return res.status(FORBIDDEN).send('Missing API token');
}
const decoded = decodeToken(req);
if (!decoded) {
return res.status(UNAUTHORIZED).send('Invalid API token');
}

const { username } = req.body;
if (!username) {
return res.status(BAD_REQUEST).send('Username field missing');
}

if (!await deleteUserFromLeaderboard(username)) {
return res.sendStatus(SERVER_ERROR);
}

AuditLog.create({
userId: decoded._id,
action: AuditLogActions.UPDATE_SIGN, // DELETE_LEETCODE_USER
details: { username },
});
return res.sendStatus(OK);
});

router.post('/checkIfUserExists', async (req, res) => {
if (!checkIfTokenSent(req)) {
return res.status(FORBIDDEN).send('Missing API token');
}
const decoded = decodeToken(req);
if (!decoded) {
return res.status(UNAUTHORIZED).send('Invalid API token');
}

const { username } = req.body;
if (!username) {
return res.status(BAD_REQUEST).send('Username field missing');
}
const check = await checkIfUserExists(username);
if (check.error) {
return res.status(check.status).send(check.message);
}
return res.status(OK).send(check.exists);
});

module.exports = router;
120 changes: 120 additions & 0 deletions api/main_endpoints/util/LeetCodeLeaderboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const logger = require('../../util/logger');
const LEETCODE_URL = 'http://192.168.69.123:8001'; // replace later obv
const { MetricsHandler } = require('../../util/metrics');

// NEED TO MODIFY THESE TO RETURN ERROR CODES AND MESSAGES FROM FASTAPI

async function healthCheck() {
try {
const url = new URL('/health-check', LEETCODE_URL);
const res = await fetch(url.href);
if (res.ok) {
const data = await res.json();
return data.status;
}
logger.error('Bad response from LeetCode Leaderboard: error code ', res.status); // fix this later
return false;
} catch (err) {
logger.error('healthCheck encountered an error: ', err);
MetricsHandler.sshTunnelErrors.inc({ type: 'LeetCode Leaderboard' });
return false;
}
}

async function getAllUsers() {
try {
const url = new URL('/getAllUsers', LEETCODE_URL);
const res = await fetch(url.href);
if (res.ok) {
const data = await res.json();
return data.users;
}
logger.error('Bad response from LeetCode Leaderboard: error code ', res.status); // fix later
return null;
} catch (err) {
logger.error('getAllUsers encountered an error: ', err);
return null;
}
}

async function addUserToLeaderboard(userData) {
try {
const url = new URL('/user/add', LEETCODE_URL);
const res = await fetch(url.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (res.ok) {
return true;
}
logger.error('Bad response from LeetCode Leaderboard: error code ', res.status); // fix later
return false;
} catch (err) {
logger.error('addUserToLeaderboard encountered an error: ', err);
return false;
}
}

async function deleteUserFromLeaderboard(username) {
try {
const url = new URL('/user/remove', LEETCODE_URL);
const res = await fetch(url.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username }),
});
if (res.ok) {
return true;
}
logger.error('Bad response from LeetCode Leaderboard: error code ', res.status); // fix later
return false;
} catch (err) {
logger.error('deleteUserFromLeaderboard encountered an error: ', err);
return false;
}
}

async function checkIfUserExists(username) {
try {
const url = new URL('/checkIfUserExists', LEETCODE_URL);
const res = await fetch(url.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username }),
});
if (res.ok) {
const data = await res.json();
return {
error: false,
exists: data.exists,
};
}
logger.error('Bad response from LeetCode Leaderboard: ', res.message); // fix later
return {
error: true,
message: res.message,
status: res.status,
};
} catch (err) {
logger.error('checkIfUserExists encountered an error: ', err);
return {
error: true,
message: err,
};
}
}

module.exports = {
healthCheck,
getAllUsers,
addUserToLeaderboard,
deleteUserFromLeaderboard,
checkIfUserExists,
};
100 changes: 100 additions & 0 deletions src/APIFunctions/LeetCodeLeaderboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ApiResponse } from './ApiResponses';
import { BASE_API_URL } from '../Enums';

export async function getAllUsers(token) {
let status = new ApiResponse();
try {
const url = new URL('/api/LeetCodeLeaderboard/getAllUsers', BASE_API_URL);
const res = await fetch(url.href, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
}
});
if (res.ok) {
const result = await res.json();
status.responseData = result;
} else {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
}
return status;
}

export async function addUser(userData, token) {
let status = new ApiResponse();
try {
const url = new URL('/api/LeetCodeLeaderboard/addUser', BASE_API_URL);
const res = await fetch(url.href, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (res.ok) {
const result = await res.json();
status.responseData = result;
} else {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
}
return status;
}

export async function deleteUser(username, token) {
let status = new ApiResponse();
try {
const url = new URL('/api/LeetCodeLeaderboard/deleteUser', BASE_API_URL);
const res = await fetch(url.href, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ username }),
});
if (res.ok) {
const result = await res.json();
status.responseData = result;
} else {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
}
return status;
}

export async function checkIfUserExists(username, token) {
let status = new ApiResponse();
try {
const url = new URL('/api/LeetCodeLeaderboard/checkIfUserExists', BASE_API_URL);
const res = await fetch(url.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ username }),
});
if (res.ok) {
const result = await res.json();
status.responseData = result;
} else {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
}
return status;
}
9 changes: 9 additions & 0 deletions src/Components/Navbar/AdminNavbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ export default function UserNavBar(props) {
</svg>
)
},
{
title: 'LeetCode Leaderboard',
route: '/leetcode-leaderboard',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="size-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.242 5.992h12m-12 6.003H20.24m-12 5.999h12M4.117 7.495v-3.75H2.99m1.125 3.75H2.99m1.125 0H5.24m-1.92 2.577a1.125 1.125 0 1 1 1.591 1.59l-1.83 1.83h2.16M2.99 15.745h1.125a1.125 1.125 0 0 1 0 2.25H3.74m0-.002h.375a1.125 1.125 0 0 1 0 2.25H2.99" />
</svg>
),
},
];

const renderRoutesForNavbar = (navbarLinks) => {
Expand Down
Loading