Skip to content

Commit 8d2e555

Browse files
Merge pull request #167 from QuCodeXClub/develop
Develop
2 parents 7ba946d + 2d4f030 commit 8d2e555

41 files changed

Lines changed: 1335 additions & 145 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Backend/openapi/openapi.yaml‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,3 +881,63 @@ paths:
881881
responses:
882882
"200":
883883
description: Message marked as read
884+
/admin/logs/activity:
885+
get:
886+
tags:
887+
- Admin
888+
summary: Get all admin activity logs
889+
security:
890+
- cookieAuth: []
891+
parameters:
892+
- name: page
893+
in: query
894+
schema:
895+
type: integer
896+
default: 1
897+
- name: limit
898+
in: query
899+
schema:
900+
type: integer
901+
default: 50
902+
responses:
903+
"200":
904+
description: Paginated list of activity logs
905+
delete:
906+
tags:
907+
- Admin
908+
summary: Clear all admin activity logs
909+
security:
910+
- cookieAuth: []
911+
responses:
912+
"200":
913+
description: Activity logs cleared successfully
914+
/admin/logs/access:
915+
get:
916+
tags:
917+
- Admin
918+
summary: Get all public access logs
919+
security:
920+
- cookieAuth: []
921+
parameters:
922+
- name: page
923+
in: query
924+
schema:
925+
type: integer
926+
default: 1
927+
- name: limit
928+
in: query
929+
schema:
930+
type: integer
931+
default: 50
932+
responses:
933+
"200":
934+
description: Paginated list of access logs
935+
delete:
936+
tags:
937+
- Admin
938+
summary: Clear all public access logs
939+
security:
940+
- cookieAuth: []
941+
responses:
942+
"200":
943+
description: Access logs cleared successfully

‎Backend/src/controllers/boardingPass.controller.js‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { ApiError } from '../utils/ApiError.js';
33
import { ApiResponse } from '../utils/ApiResponse.js';
44
import { asyncHandler } from '../utils/asyncHandler.js';
55
import { queueService } from '../services/queueService.js';
6+
import { logPublicEvent } from '../middlewares/log.middleware.js';
67

78
const generateBulkBoardingPasses = asyncHandler(async (req, res) => {
89
const { eventName, eventDescription, time, eventTime, venue, eventVenue, studentsStr } = req.body;
910
const globalTime = (time || eventTime || '').toString().trim();
1011
const globalVenue = (venue || eventVenue || '').toString().trim();
11-
12+
1213
if (!eventName || !eventDescription || !studentsStr) {
1314
throw new ApiError(400, 'Event Name, Event Description, and students data are required');
1415
}
@@ -67,6 +68,8 @@ const verifyBoardingPass = asyncHandler(async (req, res) => {
6768
throw new ApiError(404, 'Invalid Boarding Pass ID');
6869
}
6970

71+
await logPublicEvent(req, `Boarding pass verified: ${boardingPassId}`);
72+
7073
return res.status(200).json(new ApiResponse(200, boardingPass, 'Boarding pass verified successfully'));
7174
});
7275

‎Backend/src/controllers/certificate.controller.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ApiResponse } from '../utils/ApiResponse.js';
44
import { asyncHandler } from '../utils/asyncHandler.js';
55
import { queueService } from '../services/queueService.js';
66
import { uploadOnCloudinary } from '../utils/cloudinary.js';
7+
import { logPublicEvent } from '../middlewares/log.middleware.js';
78

89
const generateBulkCertificates = asyncHandler(async (req, res) => {
910
const { eventName, eventDate, coordinatorName, studentsStr, signatureImageUrl } = req.body;
@@ -75,6 +76,8 @@ const verifyCertificate = asyncHandler(async (req, res) => {
7576
throw new ApiError(404, 'Invalid Certificate ID');
7677
}
7778

79+
await logPublicEvent(req, `Certificate verified: ${certificateId}`);
80+
7881
return res.status(200).json(new ApiResponse(200, certificate, 'Certificate verified successfully'));
7982
});
8083

‎Backend/src/controllers/event.controller.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
updateOnCloudinary,
1010
getPublicIdFromUrl,
1111
} from "../utils/cloudinary.js";
12+
import { logPublicEvent } from "../middlewares/log.middleware.js";
1213

1314
const parseDateSafe = (val, fieldName, isRequired = false) => {
1415
if (!val) {
@@ -128,6 +129,8 @@ const createEvent = asyncHandler(async (req, res) => {
128129
tags: parseTags(tags),
129130
coverImage: coverImage.url,
130131
});
132+
133+
req.logAction = `Admin created event: ${event.eventName}`;
131134

132135
return res
133136
.status(201)
@@ -249,6 +252,8 @@ const getEvents = asyncHandler(async (req, res) => {
249252

250253
const totalPages = Math.ceil(total / limit) || 1;
251254

255+
await logPublicEvent(req, "Events page seen");
256+
252257
return res.status(200).json(
253258
new ApiResponse(
254259
200,
@@ -281,6 +286,8 @@ const getEventById = asyncHandler(async (req, res) => {
281286
throw new ApiError(404, "Event not found");
282287
}
283288

289+
await logPublicEvent(req, `Event details seen: ${event.eventName}`);
290+
284291
return res
285292
.status(200)
286293
.json(new ApiResponse(200, event, "Event fetched successfully"));
@@ -312,6 +319,8 @@ const deleteEvent = asyncHandler(async (req, res) => {
312319
}
313320

314321
await event.deleteOne();
322+
323+
req.logAction = `Admin deleted event: ${event.eventName}`;
315324

316325
return res
317326
.status(200)
@@ -384,6 +393,8 @@ const updateEvent = asyncHandler(async (req, res) => {
384393
event.coverImage = newCoverImageUrl;
385394

386395
await event.save();
396+
397+
req.logAction = `Admin updated event: ${event.eventName}`;
387398

388399
return res
389400
.status(200)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { SystemLog } from "../models/log.model.js";
2+
import { asyncHandler } from "../utils/asyncHandler.js";
3+
import { ApiResponse } from "../utils/ApiResponse.js";
4+
5+
// @desc Get Admin Activity Logs
6+
// @route GET /api/v1/admin/logs/activity
7+
// @access Private (Admin)
8+
export const getActivityLogs = asyncHandler(async (req, res) => {
9+
const page = parseInt(req.query.page) || 1;
10+
const limit = parseInt(req.query.limit) || 50;
11+
const skip = (page - 1) * limit;
12+
13+
const logs = await SystemLog.find({ type: 'ADMIN_ACTIVITY' })
14+
.sort({ createdAt: -1 })
15+
.skip(skip)
16+
.limit(limit);
17+
18+
const totalLogs = await SystemLog.countDocuments({ type: 'ADMIN_ACTIVITY' });
19+
20+
res.status(200).json(
21+
new ApiResponse(200, {
22+
logs,
23+
totalPages: Math.ceil(totalLogs / limit),
24+
currentPage: page,
25+
totalLogs
26+
}, "Activity logs fetched successfully")
27+
);
28+
});
29+
30+
// @desc Get Public Access Logs
31+
// @route GET /api/v1/admin/logs/access
32+
// @access Private (Admin)
33+
export const getAccessLogs = asyncHandler(async (req, res) => {
34+
const page = parseInt(req.query.page) || 1;
35+
const limit = parseInt(req.query.limit) || 50;
36+
const skip = (page - 1) * limit;
37+
38+
const logs = await SystemLog.find({ type: 'PUBLIC_ACCESS' })
39+
.sort({ createdAt: -1 })
40+
.skip(skip)
41+
.limit(limit);
42+
43+
const totalLogs = await SystemLog.countDocuments({ type: 'PUBLIC_ACCESS' });
44+
45+
res.status(200).json(
46+
new ApiResponse(200, {
47+
logs,
48+
totalPages: Math.ceil(totalLogs / limit),
49+
currentPage: page,
50+
totalLogs
51+
}, "Access logs fetched successfully")
52+
);
53+
});
54+
55+
// @desc Clear Admin Activity Logs
56+
// @route DELETE /api/v1/admin/logs/activity
57+
// @access Private (Admin)
58+
export const clearActivityLogs = asyncHandler(async (req, res) => {
59+
await SystemLog.deleteMany({ type: 'ADMIN_ACTIVITY' });
60+
61+
if (req.admin) {
62+
await SystemLog.create({
63+
type: 'ADMIN_ACTIVITY',
64+
adminId: req.admin._id,
65+
adminName: req.admin.name || 'Admin',
66+
action: 'Cleared all admin activity logs',
67+
details: { ip: req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress }
68+
});
69+
}
70+
71+
res.status(200).json(new ApiResponse(200, null, "Activity logs cleared successfully"));
72+
});
73+
74+
// @desc Clear Public Access Logs
75+
// @route DELETE /api/v1/admin/logs/access
76+
// @access Private (Admin)
77+
export const clearAccessLogs = asyncHandler(async (req, res) => {
78+
await SystemLog.deleteMany({ type: 'PUBLIC_ACCESS' });
79+
80+
if (req.admin) {
81+
await SystemLog.create({
82+
type: 'ADMIN_ACTIVITY',
83+
adminId: req.admin._id,
84+
adminName: req.admin.name || 'Admin',
85+
action: 'Cleared all public access logs',
86+
details: { ip: req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress }
87+
});
88+
}
89+
90+
res.status(200).json(new ApiResponse(200, null, "Access logs cleared successfully"));
91+
});

‎Backend/src/controllers/registration.controller.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ const updateRegistrationStatus = asyncHandler(async (req, res) => {
137137
registration.rejectionReason = finalReason;
138138
}
139139
await registration.save();
140+
141+
req.logAction = `Admin updated registration status to ${status} for Student ID: ${registration.studentId}`;
140142

141143
// Send email notification
142144
if (status === 'APPROVED') {
@@ -242,6 +244,8 @@ const updateRegistrationDetails = asyncHandler(async (req, res) => {
242244
}
243245

244246
await registration.save();
247+
248+
req.logAction = `Admin updated registration details for Student ID: ${registration.studentId}`;
245249

246250
return res
247251
.status(200)
@@ -425,10 +429,16 @@ const bulkRegistration = asyncHandler(async (req, res) => {
425429
});
426430
});
427431

432+
const logExport = asyncHandler(async (req, res) => {
433+
req.logAction = `Admin exported registrations list`;
434+
return res.status(200).json(new ApiResponse(200, null, "Export logged successfully"));
435+
});
436+
428437
export {
429438
getAllRegistrations,
430439
updateRegistrationStatus,
431440
updateRegistrationDetails,
432441
addManualRegistration,
433442
bulkRegistration,
443+
logExport,
434444
};

‎Backend/src/controllers/student.controller.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ApiError } from '../utils/ApiError.js';
44
import { ApiResponse } from '../utils/ApiResponse.js';
55
import { asyncHandler } from '../utils/asyncHandler.js';
66
import { verifyTurnstileToken } from '../utils/turnstile.js';
7+
import { logPublicEvent } from '../middlewares/log.middleware.js';
78

89
export const getRegistrationStatusHelper = async () => {
910
let setting = await SystemSetting.findOne({ key: 'registration' });
@@ -113,6 +114,8 @@ const registerStudent = asyncHandler(async (req, res) => {
113114
phone: phone.trim(),
114115
transactionId: transactionId.trim(),
115116
});
117+
118+
await logPublicEvent(req, "Student registration submitted");
116119

117120
return res.status(201).json(
118121
new ApiResponse(201, registration, 'Registration submitted successfully. Please wait for admin approval.')

‎Backend/src/controllers/team.controller.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ApiError } from '../utils/ApiError.js';
33
import { ApiResponse } from '../utils/ApiResponse.js';
44
import { asyncHandler } from '../utils/asyncHandler.js';
55
import { uploadOnCloudinary, deleteFromCloudinary, updateOnCloudinary, getPublicIdFromUrl } from '../utils/cloudinary.js';
6+
import { logPublicEvent } from '../middlewares/log.middleware.js';
67

78
const addTeamMember = asyncHandler(async (req, res) => {
89
const { academicYear, subTeam, name, post, sequenceNumber, email } = req.body;
@@ -32,6 +33,9 @@ const addTeamMember = asyncHandler(async (req, res) => {
3233
sequenceNumber: sequenceNumber ? Number(sequenceNumber) : 0,
3334
photo: photo.url,
3435
});
36+
37+
req.logAction = `Admin added a team member: ${member.name} (${member.post})`;
38+
3539
return res.status(201).json(new ApiResponse(201, member, 'Team member added successfully'));
3640
});
3741
const getTeamMembers = asyncHandler(async (req, res) => {
@@ -42,6 +46,8 @@ const getTeamMembers = asyncHandler(async (req, res) => {
4246

4347
const members = await TeamMember.find(query).select('-email').sort({ subTeam: 1, sequenceNumber: 1 });
4448

49+
await logPublicEvent(req, "Team page seen");
50+
4551
return res.status(200).json(new ApiResponse(200, members, 'Team members fetched successfully'));
4652
});
4753

@@ -58,6 +64,8 @@ const deleteTeamMember = asyncHandler(async (req, res) => {
5864

5965
await deleteFromCloudinary(publicId);
6066
await member.deleteOne();
67+
68+
req.logAction = `Admin deleted team member: ${member.name}`;
6169

6270
return res.status(200).json(new ApiResponse(200, {}, 'Team member deleted successfully'));
6371
});
@@ -91,6 +99,9 @@ const updateTeamMember = asyncHandler(async (req, res) => {
9199
member.sequenceNumber = sequenceNumber !== undefined ? Number(sequenceNumber) : member.sequenceNumber;
92100
member.photo = newPhotoUrl;
93101
await member.save();
102+
103+
req.logAction = `Admin updated team member: ${member.name}`;
104+
94105
return res.status(200).json(new ApiResponse(200, member, 'Team member updated successfully'));
95106
});
96107

0 commit comments

Comments
 (0)