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

// GET unverified achievements by type
exports.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
exports.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();

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
exports.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
exports.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,
});

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 (endorsed + unendorsed)
exports.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." });
}
};
265 changes: 265 additions & 0 deletions backend/controllers/announcementController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
const mongoose = require("mongoose");
const {
Announcement,
Event,
OrganizationalUnit,
Position,
} = require("../models/schema");

// Helper function
const findTargetId = async (type, identifier) => {
let target = null;

const isObjectId = mongoose.Types.ObjectId.isValid(identifier);
const objectId = isObjectId ? new mongoose.Types.ObjectId(identifier) : null;
if (type === "Event") {
target = await Event.findOne({
$or: [
...(objectId ? [{ _id: identifier }] : []),
{ event_id: identifier },
],
});
} else if (type === "Organizational_Unit") {
target = await OrganizationalUnit.findOne({
$or: [
...(objectId ? [{ _id: identifier }] : []),
{ unit_id: identifier },
],
});
} else if (type === "Position") {
target = await Position.findOne({
$or: [
...(objectId ? [{ _id: identifier }] : []),
{ position_id: identifier },
],
});
}

return target ? target._id : null;
};

exports.createAnnouncement = async (req, res) => {
try {
const {
title,
content,
type = "General",
isPinned,
targetIdentifier,
} = req.body;
let targetId = null;

if (type != "General" && targetIdentifier) {
targetId = await findTargetId(type, targetIdentifier);
if (!targetId) {
return res
.status(404)
.json({ error: `No ${type} found with that identifier` });
}
}

const newAnnouncement = new Announcement({
author: req.user._id,
content,
is_pinned: isPinned || false,
title,
type: type || "General",
target_id: targetId,
});
await newAnnouncement.save();
res.status(201).json(newAnnouncement);
} catch (error) {
console.error("Error creating announcement:", error);
res.status(500).json({ error: "Failed to create announcement" });
}
};

exports.getAnnouncements = async (req, res) => {
try {
const {
type,
author,
isPinned,
search,
page = 1,
limit = 10,
sortBy = "createdAt",
sortOrder = "desc",
} = req.query;

const filter = {};

if (type && type != "All") filter.type = type;
if (author) filter.author = author;
if (typeof isPinned !== "undefined") {
// accept true/false or 1/0
const val = `${isPinned}`.toLowerCase();
filter.is_pinned = val === "true" || val === "1";
}

if (search) {
const regex = new RegExp(search, "i");
filter.$or = [{ title: regex }, { content: regex }];
}

const pageNum = Math.max(parseInt(page, 10) || 1, 1);
const limNum = Math.max(parseInt(limit, 10) || 10, 1);

const sortDirection = sortOrder === "asc" ? 1 : -1;
const sort = { [sortBy]: sortDirection };

const total = await Announcement.countDocuments(filter);
const query = Announcement.find(filter)
.sort(sort)
.skip((pageNum - 1) * limNum)
.limit(limNum)
.populate("author", "username personal_info.email personal_info.name");

if (filter.type && filter.type !== "General") {
query.populate("target_id");
}
const announcements = await query;

res.json({
total,
page: pageNum,
limit: limNum,
totalPages: Math.ceil(total / limNum) || 0,
announcements,
});
// console.log(announcements);
} catch (error) {
console.error("Error fetching announcements:", error);
res.status(500).json({ error: "Failed to fetch announcements" });
}
};

exports.getAnnouncementById = async (req, res) => {
try {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ error: "Invalid announcement id" });
}

const announcement = await Announcement.findById(id)
.populate("author", "username personal_info.email personal_info.name")
.populate("target_id");

if (!announcement) {
return res.status(404).json({ error: "Announcement not found" });
}

res.json(announcement);
} catch (error) {
console.error("Error fetching announcement by id:", error);
res.status(500).json({ error: "Failed to fetch announcement" });
}
};

exports.updateAnnouncement = async (req, res) => {
try {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ error: "Invalid announcement id" });
}

const announcement = await Announcement.findById(id);
if (!announcement) {
return res.status(404).json({ error: "Announcement not found" });
}

// Only the author
const isAuthor =
announcement.author &&
announcement.author.toString() === req.user._id.toString();
if (!isAuthor) {
return res
.status(403)
.json({ error: "Forbidden: cannot edit this announcement" });
}

const { title, content, type, targetIdentifier, isPinned } = req.body;
if (title !== undefined) announcement.title = title;
if (content !== undefined) announcement.content = content;
if (isPinned !== undefined) announcement.is_pinned = Boolean(isPinned);

if (type || targetIdentifier) {
const newType = type || announcement.type;

const newIdentifier =
targetIdentifier ||
(announcement.target_id ? announcement.target_id.toString() : null);

if (newType === "General") {
announcement.type = "General";
announcement.target_id = null;
} else {
if (!newIdentifier) {
return res.status(400).json({
error:
"targetIdentifier is required when setting a non-General type",
});
}
const newTargetId = await findTargetId(newType, newIdentifier);
if (!newTargetId) {
return res.status(404).json({
error: `Target ${newType} not found with identifier ${newIdentifier}`,
});
}
announcement.target_id = newTargetId;
announcement.type = newType;
}
}

announcement.updatedAt = Date.now();
await announcement.save();

const populated = await announcement.populate([
{
path: "author",
select: "username personal_info.email personal_info.name",
},
]);
if (announcement.type !== "General") {
await populated.populate("target_id");
}
res.json(populated);
} catch (error) {
console.error("Error updating announcement:", error);
res.status(500).json({ error: "Failed to update announcement" });
}
};

exports.deleteAnnouncement = async (req, res) => {
try {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ error: "Invalid announcement id" });
}

const announcement = await Announcement.findById(id);
if (!announcement) {
return res.status(404).json({ error: "Announcement not found" });
}

// Only the author
const isAuthor =
announcement.author &&
announcement.author.toString() === req.user._id.toString();
if (!isAuthor) {
return res
.status(403)
.json({ error: "Forbidden: cannot delete this announcement" });
}

await Announcement.deleteOne({ _id: id });

res.json({ message: "Announcement deleted", id });
} catch (error) {
console.error("Error deleting announcement:", error);
res.status(500).json({ error: "Failed to delete announcement" });
}
};
Loading