Skip to content

Commit 28d28c2

Browse files
committed
Reimplemented challenge rating logic
1 parent fb55dd7 commit 28d28c2

3 files changed

Lines changed: 83 additions & 57 deletions

File tree

src/backend/db.js

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -306,21 +306,20 @@ async function GetChallenges() {
306306
}
307307
export default GetChallenges;
308308

309-
// data = { challenge_name }
309+
// data = { challenge_id }
310310
async function GetChallengeInfo(data) {
311-
const challengeName = SanitizeString(data.challenge_name)
312-
if (challengeName === null) {
313-
return null
314-
} else {
315-
const challengeProfile = await ChallengeCollection.findOne({ name: challengeName.replaceAll('_', ' ') })
316-
311+
const challengeID = SanitizeAlphaNumeric(data.challenge_id)
312+
const challengeProfile = await ChallengeCollection.findOne({ _id: challengeID })
313+
if (challengeProfile) {
317314
return {
318315
"name": challengeProfile.name,
319316
"description": challengeProfile.description,
320317
"category": challengeProfile.category,
321318
"difficulty": challengeProfile.difficulty,
322319
"rating": challengeProfile.rating,
323320
}
321+
} else {
322+
return null;
324323
}
325324
}
326325

@@ -1364,20 +1363,25 @@ async function ConvertCompletions(userCompletions, teamCompletions) {
13641363
}
13651364

13661365
async function UserRatingChallenge(ratingData, jwt) {
1367-
const userProfile = await UserCollection.findOne({ username: jwt.username, email: jwt.email })
1368-
if (!userProfile || !ratingData) {
1366+
const username = SanitizeString(jwt.username);
1367+
const email = SanitizeString(jwt.email);
1368+
1369+
if (!ratingData || username === null || email === null) {
1370+
console.log("[-] Error rating username or email in JWT malformed!");
1371+
return null;
1372+
}
1373+
1374+
const userProfile = await UserCollection.findOne({ username: username, email: email })
1375+
if (!userProfile) {
1376+
console.log("[-] Profile could not be Found!");
13691377
return null;
13701378
}
13711379

13721380
// check if the userProfile completed the challenge theyre rating
13731381
let completedChallenge = false;
13741382

13751383
// check that numberRating is a valid number
1376-
ratingData.challenge_name = SanitizeString(ratingData.challenge_name);
1377-
if (ratingData.challenge_name === null) {
1378-
console.log("[-] Error rating challenge_name attribute malformed!");
1379-
return null;
1380-
}
1384+
const challengeID = SanitizeAlphaNumeric(ratingData.challenge_id);
13811385

13821386
// Check if numberRating is a valid number
13831387
if ( !ValidRatingNumber(ratingData.rating) ) {
@@ -1387,27 +1391,26 @@ async function UserRatingChallenge(ratingData, jwt) {
13871391
}
13881392

13891393
const numberRating = ratingData.rating;
1390-
const challengeName = ratingData.challenge_name.replaceAll('_', ' ');
13911394

1392-
console.log("Rating Challenge: " + challengeName);
1395+
console.log("Rating Challenge ID: " + challengeID);
13931396
console.log("|______" + numberRating);
13941397

13951398
// check if this user has already rated the challenge
13961399
// in ratingData
1397-
if (userProfile.ratings.includes(challengeName)) {
1398-
console.log("[*] " + userProfile.username + " has already submitted a rating for: " + challengeName);
1400+
if (userProfile.ratings.includes(challengeID)) {
1401+
console.log("[*] " + userProfile.username + " has already submitted a rating for challenge_id: " + challengeID);
13991402
return null;
14001403
}
14011404

14021405
// iterate the users completions
14031406
for (const data of Object.entries(userProfile.completions)) {
1404-
const [index, { name, time }] = data; // break down the entry
1405-
const challengeProfile = await ChallengeCollection.findOne({ name: name.replaceAll('_', ' ') })
1407+
const [index, { id, time }] = data; // break down the entry
1408+
const challengeProfile = await ChallengeCollection.findOne({ _id: SanitizeAlphaNumeric(id) })
14061409

14071410
if (challengeProfile) {
1408-
// users completions have the challenge name
1409-
// listed as completed
1410-
if (challengeProfile.name === challengeName) {
1411+
// users completions have the challenge id listed as completed
1412+
if (challengeProfile._id.toString() === SanitizeAlphaNumeric(id)) {
1413+
console.log("[*] User has completed this challenge!")
14111414
completedChallenge = true;
14121415
break;
14131416
}
@@ -1416,23 +1419,24 @@ async function UserRatingChallenge(ratingData, jwt) {
14161419

14171420
// they didnt complete the challenge
14181421
if (!completedChallenge) {
1422+
console.log("[-] User tried rating a challenge they have not completed!")
14191423
return null;
14201424
} else {
14211425
// they completed the challenge we can take their number rating
14221426
// and apply it to the challenge entry in the db
14231427
await ChallengeCollection.updateOne(
1424-
{ name: challengeName },
1428+
{ _id: challengeID },
14251429
{ $push: { user_rates: Number(numberRating) } } // user_rates are used to calulate rating attribute
1426-
)
1430+
);
14271431

14281432
// update the challenges rating attribute based on its user_rates
1429-
const updatedChallenge = await ChallengeCollection.findOne({ name: challengeName });
1433+
const updatedChallenge = await ChallengeCollection.findOne({ _id: challengeID });
14301434
if (updatedChallenge && updatedChallenge.user_rates.length > 0) {
14311435
const total = updatedChallenge.user_rates.reduce((sum, r) => sum + r, 0);
14321436
const avg = total / updatedChallenge.user_rates.length;
14331437

14341438
await ChallengeCollection.updateOne(
1435-
{ name: challengeName },
1439+
{ _id: challengeID },
14361440
{ $set: { rating: avg } }
14371441
);
14381442
}
@@ -1441,8 +1445,10 @@ async function UserRatingChallenge(ratingData, jwt) {
14411445
// they cannot spam ratings for a challenge
14421446
await UserCollection.updateOne(
14431447
{ _id: userProfile._id },
1444-
{ $addToSet: { ratings: challengeName } }
1445-
)
1448+
{ $addToSet: { ratings: challengeID } }
1449+
);
1450+
1451+
console.log("[+] Rate Uploaded Successfully!")
14461452

14471453
return {
14481454
"message": "Rate Uploaded Successfully!"

src/backend/server.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ app.get('/challenges', async (req, res) => {
132132

133133
app.post('/challenge', async (req, res) => {
134134
const data = req.body;
135+
// fetches details from given challenge id
135136
const challenge_details = await GetChallengeInfo(data);
136137
res.json(challenge_details);
137138
});
@@ -560,7 +561,8 @@ app.post('/rate-challenge', async (req, res) => {
560561
try {
561562
const ratingChallenge = await UserRatingChallenge(data, validJWT);
562563
return res.json(ratingChallenge);
563-
} catch {
564+
} catch (err) {
565+
console.error(err)
564566
return res.json(null);
565567
}
566568
} else {

src/pages/rate_challenge.js

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function RatingPage() {
2222
let unratedChalls = data.completions;
2323

2424
if (data.user_rates) {
25-
unratedChalls = unratedChalls.filter(c => !data.user_rates.includes(c.name.replace(/_/g, " ")));
25+
unratedChalls = unratedChalls.filter(c => !data.user_rates.includes(c.id));
2626
}
2727
setUnratedChallenges(unratedChalls);
2828
}
@@ -35,14 +35,14 @@ export function RatingPage() {
3535
GetProfileDetails();
3636
}, []); // runs once when the component mounts
3737

38-
async function GetChallengeDetails(challengeName) {
38+
async function GetChallengeDetails(challengeID) {
3939
try {
4040
const response = await fetch(`http://${GetBackendHost()}/challenge`, {
4141
method: "POST",
4242
headers: {
4343
"Content-Type": "application/json",
4444
},
45-
body: JSON.stringify({ "challenge_name": challengeName }),
45+
body: JSON.stringify({ "challenge_id": challengeID }),
4646
credentials: 'include' // ensures cookies are sent
4747
});
4848

@@ -54,49 +54,68 @@ export function RatingPage() {
5454
}
5555

5656
// Fetch the details for each challenge when the component mounts
57-
const fetchChallengeData = async (challengeName) => {
58-
const data = await GetChallengeDetails(challengeName);
59-
setChallengeDetails((prevData) => ({
60-
...prevData,
61-
[challengeName]: data
62-
}));
57+
const fetchChallengeData = async (challengeID) => {
58+
const data = await GetChallengeDetails(challengeID);
59+
if (data) {
60+
setChallengeDetails((prevData) => ({
61+
...prevData,
62+
[challengeID]: data
63+
}));
64+
}
6365
};
6466

6567
// Handle rating change for each challenge
66-
const handleRatingChange = (challengeName, newRating) => {
68+
const handleRatingChange = (challengeID, newRating) => {
6769
setRatings((prevRatings) => ({
6870
...prevRatings,
69-
[challengeName]: newRating,
71+
[challengeID]: newRating,
7072
}));
7173
};
7274

7375
// Handle form submission for each challenge
74-
const handleSubmit = async (event, challengeName) => {
76+
const handleSubmit = async (event, challengeID) => {
77+
event.preventDefault();
78+
let msgArea = document.getElementById('msg_popup');
79+
7580
try {
7681
const response = await fetch(`http://${GetBackendHost()}/rate-challenge`, {
7782
method: "POST",
7883
headers: {
7984
"Content-Type": "application/json",
8085
},
8186
body: JSON.stringify({
82-
"challenge_name": challengeName,
83-
"rating": ratings[challengeName],
87+
"challenge_id": challengeID,
88+
"rating": ratings[challengeID],
8489
}),
8590
credentials: 'include' // ensures cookies are sent
8691
});
8792

8893
const data = await response.json();
94+
if (data) {
95+
if (msgArea) {
96+
msgArea.innerHTML = "<p style='color: green;'>" + data.message + "</p>";
97+
GetProfileDetails();
98+
}
99+
} else {
100+
if (msgArea) {
101+
msgArea.innerHTML = "<p style='color: red;'>Error Rating Challenge!</p>";
102+
}
103+
}
89104
} catch (error) {
90105
console.error("Error sending request:", error);
106+
if (msgArea) {
107+
msgArea.innerHTML = "<p style='color: red;'>Error Rating Challenge!</p>";
108+
}
91109
}
92110
};
93111

94112
return (
95113
<div className="App">
96114
<Navbar />
97115
<div className="container mt-4">
116+
<div id='msg_popup' style={{ fontSize: "2rem", fontWeight: 'bold' }}>
117+
</div>
98118
<h2 className="mb-3">Rating Page</h2>
99-
100119
<div className="row justify-content-center">
101120
{(profileData && unratedChallenges.length > 0) ? (
102121
<>
@@ -108,26 +127,25 @@ export function RatingPage() {
108127
>
109128
<div className="card h-100 shadow-sm p-2">
110129
<div className="card-body p-2">
111-
<h6 className="card-title mb-1">{challenge.name.replaceAll('_', ' ')}</h6>
112-
113130
{/* Fetch challenge details if not already loaded */}
114-
{challengeDetails[challenge.name] ? (
131+
{challengeDetails[challenge.id] ? (
115132
<>
133+
<h6 className="card-title mb-1">{challengeDetails[challenge.id].name}</h6>
116134
<small className="text-muted">
117-
{challengeDetails[challenge.name].category} | Difficulty: {challengeDetails[challenge.name].difficulty}
135+
{challengeDetails[challenge.id].category} | Difficulty: {challengeDetails[challenge.id].difficulty}
118136
</small>
119-
<p className="card-text small mt-2">{challengeDetails[challenge.name].description}</p>
137+
<p className="card-text small mt-2">{challengeDetails[challenge.id].description}</p>
120138
<p className="card-text small mb-1">
121-
{challengeDetails[challenge.name].rating} / 5
139+
{challengeDetails[challenge.id].rating} / 5
122140
</p>
123141

124-
<form onSubmit={(event) => handleSubmit(event, challenge.name)}>
142+
<form onSubmit={(event) => handleSubmit(event, challenge.id)}>
125143
<div>
126-
<label htmlFor={`rating-${challenge.name}`}>Rating (1-5): </label>
144+
<label htmlFor={`rating-${challenge.id}`}>Rating (1-5): </label>
127145
<select
128-
id={`rating-${challenge.name}`}
129-
value={ratings[challenge.name] || "select rating"} // default rating if not set
130-
onChange={(e) => handleRatingChange(challenge.name, e.target.value)}
146+
id={`rating-${challenge.id}`}
147+
value={ratings[challenge.id] || "select rating"} // default rating if not set
148+
onChange={(e) => handleRatingChange(challenge.id, e.target.value)}
131149
>
132150
{["select rating", 1, 2, 3, 4, 5].map((value) => (
133151
<option key={value} value={value}>
@@ -148,7 +166,7 @@ export function RatingPage() {
148166
) : (
149167
<>
150168
<p>Loading Challenge Info. . .</p>
151-
{fetchChallengeData(challenge.name)} {/* Fetch the data on load */}
169+
{fetchChallengeData(challenge.id)} {/* Fetch the data on load */}
152170
</>
153171
)}
154172
</div>

0 commit comments

Comments
 (0)