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
162 changes: 162 additions & 0 deletions backend/controllers/achievementController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
const { Achievement } = require("../models/schema");
const { v4: uuidv4 } = require("uuid");

// GET unverified achievements by type
const getUnendorsedAchievements = async (req, res) => {
const { type } = req.params;

try {
const unverifiedAchievements = await Achievement.find({
type,
verified: false,
})
.populate("user_id", "personal_info.name username user_id")
.populate("event_id", "title description ");

res.json(unverifiedAchievements);
} catch (err) {
console.error(err);
res
.status(500)
.json({ message: "Failed to fetch unverified achievements." });
}
};

// PATCH verify achievement by ID
const verifyAchievement = async (req, res) => {
const { id } = req.params;
const { verified_by } = req.body;

try {
const achievement = await Achievement.findById(id);

if (!achievement) {
return res.status(404).json({
message: "Achievement not found.",
});
}

achievement.verified = true;
achievement.verified_by = verified_by;

await achievement.save();
Comment on lines +26 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Derive verified_by from the authenticated user, not the request body.

Line 28 lets the caller choose verified_by, so an admin can verify an achievement while attributing it to someone else. That breaks the audit trail. Set this field from the authenticated principal instead of trusting client input.

Suggested fix
 const verifyAchievement = async (req, res) => {
   const { id } = req.params;
-  const { verified_by } = req.body;
+  const verified_by = req.user._id; // or req.user.user_id, matching the schema

   try {
     const achievement = await Achievement.findById(id);
@@
     achievement.verified = true;
     achievement.verified_by = verified_by;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const verifyAchievement = async (req, res) => {
const { id } = req.params;
const { verified_by } = req.body;
try {
const achievement = await Achievement.findById(id);
if (!achievement) {
return res.status(404).json({
message: "Achievement not found.",
});
}
achievement.verified = true;
achievement.verified_by = verified_by;
await achievement.save();
const verifyAchievement = async (req, res) => {
const { id } = req.params;
const verified_by = req.user._id; // or req.user.user_id, matching the schema
try {
const achievement = await Achievement.findById(id);
if (!achievement) {
return res.status(404).json({
message: "Achievement not found.",
});
}
achievement.verified = true;
achievement.verified_by = verified_by;
await achievement.save();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/controllers/achievementController.js` around lines 26 - 42, In
verifyAchievement, stop trusting req.body.verified_by and instead set
achievement.verified_by from the authenticated principal (e.g. req.user.id or
req.user._id) obtained from the request context; remove or ignore the {
verified_by } destructuring and any use of req.body for that field, validate
that req.user exists before assigning, and persist the change via
achievement.save() so the audit trails correctly reflect the authenticating
user.


res.json({
message: "Achievement verified successfully.",
achievement,
});
} catch (err) {
console.error(err);
res.status(500).json({
message: "Failed to verify achievement.",
});
}
};

// REJECT (delete) achievement by ID
const rejectAchievement = async (req, res) => {
const { id } = req.params;

try {
const deletedAchievement = await Achievement.findByIdAndDelete(id);

if (!deletedAchievement) {
return res.status(404).json({
message: "Achievement not found.",
});
}

res.json({
message: "Achievement rejected and deleted successfully.",
});
} catch (err) {
console.error("Failed to reject achievement:", err);

res.status(500).json({
message: "Failed to reject achievement.",
});
}
};

// Add achievement
const addAchievement = async (req, res) => {
try {
const {
title,
description,
category,
type,
level,
date_achieved,
position,
certificate_url,
event_id,
user_id,
} = req.body;

if (!title || !category || !date_achieved || !user_id) {
return res.status(400).json({
message: "Missing required fields",
});
}

const achievement = new Achievement({
achievement_id: uuidv4(),
user_id,
title,
description,
category,
type,
level,
date_achieved,
position,
certificate_url,
event_id: event_id || null,
});
Comment on lines +82 to +115
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block cross-user achievement creation.

Line 94 accepts user_id from the body on an endpoint that only requires authentication. As written, any logged-in student can create achievements for another user. Enforce req.user ownership here and only allow admins to override the target user.

Suggested fix
 const addAchievement = async (req, res) => {
   try {
     const {
@@
-      user_id,
+      user_id,
     } = req.body;
+
+    const effectiveUserId =
+      req.user.role === "admin" ? user_id : req.user._id; // match the schema field

-    if (!title || !category || !date_achieved || !user_id) {
+    if (!title || !category || !date_achieved || !effectiveUserId) {
       return res.status(400).json({
         message: "Missing required fields",
       });
     }
@@
     const achievement = new Achievement({
       achievement_id: uuidv4(),
-      user_id,
+      user_id: effectiveUserId,
       title,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/controllers/achievementController.js` around lines 82 - 115, In
addAchievement, do not trust req.body.user_id for ownership: validate and
enforce that the achievement's user_id is taken from req.user.id (or equivalent)
unless the authenticated user has an admin role; if req.user.role indicates
admin, allow using req.body.user_id as an override, otherwise ignore
req.body.user_id and set user_id = req.user.id before creating the Achievement
and saving it. Ensure you reference addAchievement, req.user, and any role-check
helper (e.g., req.user.role or isAdmin) when implementing the change and return
403 if a non-admin attempts to set a different user_id.


await achievement.save();

return res.status(201).json({
message: "Achievement saved successfully",
achievement,
});
} catch (error) {
console.error("Error saving achievement:", error);

return res.status(500).json({
message: "Server error",
});
}
};

// Get all user achievements
const getUserAchievements = async (req, res) => {
const userId = req.params.userId;

try {
const userAchievements = await Achievement.find({
user_id: userId,
})
.populate("event_id", "title description")
.populate(
"verified_by",
"personal_info.name username user_id"
);

res.json(userAchievements);
} catch (err) {
console.error("Failed to get user Achievements:", err);

res.status(500).json({
message: "Failed to get user Achievements.",
});
}
};

module.exports = {
getUnendorsedAchievements,
verifyAchievement,
rejectAchievement,
addAchievement,
getUserAchievements,
};
Loading
Loading