Skip to content

Commit 3d2d87a

Browse files
committed
fix: resolved 400 error when trying to search notification by id. fixed issues with lead application guard not allowing users register to be leadd. the userguard was removed from this endpoint, since it first checks the request is from a lead or an admin
1 parent 0d8960e commit 3d2d87a

13 files changed

Lines changed: 559 additions & 35 deletions

src/events/events.admin.controller.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
2+
Body,
23
Controller,
34
Delete,
45
Param,
56
Patch,
67
Request,
78
UseGuards,
89
} from '@nestjs/common';
9-
import { ApiBearerAuth, ApiParam, ApiTags } from '@nestjs/swagger';
10+
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
1011
import { JwtAdminsGuard } from 'src/shared/auth/guards/jwt.admins.guard';
1112
import { ApiReq } from 'src/shared/interfaces';
1213
import { EventService } from './events.users.service';
@@ -25,6 +26,35 @@ export class EventAdminsController {
2526
return this.eventService.approveEvent(id, admin_id);
2627
}
2728

29+
@ApiBearerAuth()
30+
@UseGuards(JwtAdminsGuard)
31+
@Patch('event/:id/reject')
32+
@ApiParam({ name: 'id', type: 'string' })
33+
@ApiOperation({
34+
summary: 'Reject an event',
35+
description: 'Reject an event with a reason',
36+
})
37+
@ApiBody({
38+
schema: {
39+
type: 'object',
40+
properties: {
41+
message: {
42+
type: 'string',
43+
description: 'Reason for rejection',
44+
},
45+
},
46+
},
47+
})
48+
async rejectEvent(
49+
@Param('id') id: string,
50+
@Request() req: ApiReq,
51+
@Body() body: { message?: string },
52+
) {
53+
const admin_id = req.user._id.toString();
54+
const message = body.message || 'Event rejected';
55+
return this.eventService.rejectEvent(id, admin_id, message);
56+
}
57+
2858
@ApiBearerAuth()
2959
@UseGuards(JwtAdminsGuard)
3060
@Delete('event/:id')

src/events/events.service.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ describe('EventService', () => {
6363
createNotification: jest.fn().mockResolvedValue(undefined),
6464
getNotificationByUserId: jest.fn().mockResolvedValue(undefined),
6565
resolveNotification: jest.fn().mockResolvedValue(undefined),
66+
notifyOtherAdminsOfResolution: jest.fn().mockResolvedValue(undefined),
67+
createAdminNotificationForNewRequest: jest.fn().mockResolvedValue(undefined),
6668
};
6769

6870
const module: TestingModule = await Test.createTestingModule({
@@ -109,6 +111,7 @@ describe('EventService', () => {
109111
expect.objectContaining({
110112
receiverId: eventDto.host,
111113
message: expect.stringContaining(eventDto.title),
114+
isAdminNotification: true,
112115
}),
113116
);
114117
expect(result).toEqual(mockEvent);
@@ -215,6 +218,12 @@ describe('EventService', () => {
215218
message,
216219
'APPROVED',
217220
);
221+
expect(notificationsServiceMock.notifyOtherAdminsOfResolution).toHaveBeenCalledWith(
222+
mockEvent._id.toString(),
223+
admin_id,
224+
'approved',
225+
'EVENTS_REQUEST',
226+
);
218227
});
219228

220229
it('should throw NotFoundException if event not found', async () => {

src/events/events.users.service.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,65 @@ export class EventService {
130130
message,
131131
'APPROVED',
132132
);
133+
134+
// Notify other admins about the resolution
135+
await this.notificationsService.notifyOtherAdminsOfResolution(
136+
event._id.toString(),
137+
admin_id,
138+
'approved',
139+
NotificationType.events,
140+
);
141+
133142
return approveEvent;
134143
}
144+
145+
async rejectEvent(
146+
id: string,
147+
admin_id: string,
148+
message: string = 'Event rejected',
149+
): Promise<Event> {
150+
const rejectEvent = await this.eventModel.findByIdAndUpdate(
151+
id,
152+
{ status: Status.REJECTED },
153+
{ new: true },
154+
);
155+
156+
if (!rejectEvent) {
157+
throw new NotFoundException(`Event with ID ${id} not found`);
158+
}
159+
160+
const event = await this.eventModel.findById({ _id: id });
161+
const existingNotification =
162+
await this.notificationsService.getNotificationByUserId(
163+
event.host,
164+
event._id.toString(),
165+
);
166+
await this.notificationsService.resolveNotification(
167+
existingNotification._id.toString(),
168+
admin_id,
169+
message,
170+
'REJECTED',
171+
);
172+
173+
// Notify other admins about the resolution
174+
await this.notificationsService.notifyOtherAdminsOfResolution(
175+
event._id.toString(),
176+
admin_id,
177+
'rejected',
178+
NotificationType.events,
179+
);
180+
181+
// Create notification for the user about rejection
182+
await this.notificationsService.createNotification({
183+
receiverId: event.host,
184+
notification_type: NotificationType.events,
185+
entityId: event._id.toString(),
186+
message: `Your event "${event.title}" has been rejected: ${message}`,
187+
data: { eventTitle: event.title, reason: message },
188+
isRead: false,
189+
isAdminNotification: false,
190+
});
191+
192+
return rejectEvent;
193+
}
135194
}

src/notifications/notifications.controller.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ describe('NotificationsController', () => {
1414
{ message: 'Test notification 1' },
1515
{ message: 'Test notification 2' },
1616
]),
17+
getNotificationById: jest
18+
.fn()
19+
.mockResolvedValue({ notificationId: 'id', parentId: 'parent-id' }),
20+
userReadNotification: jest.fn().mockResolvedValue(true),
21+
userClearNotifications: jest.fn().mockResolvedValue(true),
22+
getAdminNotifications: jest.fn().mockResolvedValue([
23+
{ message: 'Admin notification 1' },
24+
{ message: 'Admin notification 2' },
25+
]),
26+
adminReadNotification: jest.fn().mockResolvedValue(true),
1727
};
1828
const module: TestingModule = await Test.createTestingModule({
1929
controllers: [NotificationsController],
@@ -37,5 +47,44 @@ describe('NotificationsController', () => {
3747
{ message: 'Test notification 1' },
3848
{ message: 'Test notification 2' },
3949
]);
50+
expect(service.getNotifications).toHaveBeenCalledWith('1234');
51+
});
52+
53+
it('should get notification parent id', async () => {
54+
const result = await controller.getNotificationParentId('notification-id');
55+
expect(result).toEqual({ notificationId: 'id', parentId: 'parent-id' });
56+
expect(service.getNotificationById).toHaveBeenCalledWith('notification-id');
57+
});
58+
59+
it('should mark notification as read', async () => {
60+
const req = { user: { _id: 'user-id' } } as any;
61+
const result = await controller.markNotificationAsRead('notification-id', req);
62+
expect(result).toBe(true);
63+
expect(service.userReadNotification).toHaveBeenCalledWith(
64+
'notification-id',
65+
'user-id',
66+
);
67+
});
68+
69+
it('should clear user notifications', async () => {
70+
const req = { user: { _id: 'user-id' } } as any;
71+
const result = await controller.clearUserNotifications(req);
72+
expect(result).toBe(true);
73+
expect(service.userClearNotifications).toHaveBeenCalledWith('user-id');
74+
});
75+
76+
it('should get admin notifications', async () => {
77+
const result = await controller.getAdminNotifications('pending');
78+
expect(result).toEqual([
79+
{ message: 'Admin notification 1' },
80+
{ message: 'Admin notification 2' },
81+
]);
82+
expect(service.getAdminNotifications).toHaveBeenCalledWith('pending');
83+
});
84+
85+
it('should mark admin notification as read', async () => {
86+
const result = await controller.markAdminNotificationAsRead('notification-id');
87+
expect(result).toBe(true);
88+
expect(service.adminReadNotification).toHaveBeenCalledWith('notification-id');
4089
});
4190
});
Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
1-
import { Controller, Get, Param, Query, Request } from '@nestjs/common';
2-
import { ApiBearerAuth, ApiQuery, ApiResponse } from '@nestjs/swagger';
1+
import {
2+
Controller,
3+
Delete,
4+
Get,
5+
Param,
6+
Patch,
7+
Query,
8+
Request,
9+
UseGuards,
10+
} from '@nestjs/common';
11+
import {
12+
ApiBearerAuth,
13+
ApiOperation,
14+
ApiParam,
15+
ApiQuery,
16+
ApiResponse,
17+
} from '@nestjs/swagger';
18+
import { JwtAdminsGuard } from 'src/shared/auth/guards/jwt.admins.guard';
19+
import { JwtUsersGuard } from 'src/shared/auth/guards/jwt.users.guard';
320
import { ApiReq } from 'src/shared/interfaces';
421
import { NotificationsService } from './notifications.service';
522

@@ -8,20 +25,27 @@ import { NotificationsService } from './notifications.service';
825
export class NotificationsController {
926
constructor(private readonly notificationsService: NotificationsService) {}
1027

11-
@Get('/notifications')
28+
@ApiBearerAuth()
29+
@ApiOperation({
30+
summary: 'get all notifications',
31+
description:
32+
'returns all notifications based on the user type making the request',
33+
})
1234
@ApiResponse({
1335
status: 200,
1436
description: 'List of notifications',
1537
})
38+
@UseGuards(JwtUsersGuard)
39+
@Get()
1640
async getNotifications(@Request() req: ApiReq): Promise<string[]> {
17-
// fetch notifications for user
1841
const notifications = await this.notificationsService.getNotifications(
1942
req.user._id.toString(),
2043
);
2144
return notifications;
2245
}
2346

24-
@Get('/notifications/:notificationId')
47+
@UseGuards(JwtUsersGuard)
48+
@Get(':notificationId')
2549
@ApiResponse({
2650
status: 200,
2751
description: 'Return the id of the object that prompted the notification',
@@ -33,4 +57,83 @@ export class NotificationsController {
3357
await this.notificationsService.getNotificationById(notificationId);
3458
return notification;
3559
}
60+
61+
@ApiBearerAuth()
62+
@ApiOperation({
63+
summary:
64+
'Mark notification as read for users (DOES NOT WORK FOR ADMIN NOTIFICATIONS)',
65+
description:
66+
'Marks a specific notification as read for the requesting user',
67+
})
68+
@ApiParam({ name: 'notificationId', type: 'string' })
69+
@ApiResponse({
70+
status: 200,
71+
description: 'Notification marked as read',
72+
})
73+
@UseGuards(JwtUsersGuard)
74+
@Patch(':notificationId/read')
75+
async markNotificationAsRead(
76+
@Param('notificationId') notificationId: string,
77+
@Request() req: ApiReq,
78+
): Promise<boolean> {
79+
return await this.notificationsService.userReadNotification(
80+
notificationId,
81+
req.user._id.toString(),
82+
);
83+
}
84+
85+
@ApiBearerAuth()
86+
@ApiOperation({
87+
summary: 'Clear all notifications for user',
88+
description: 'Deletes all notifications for the requesting user',
89+
})
90+
@ApiResponse({
91+
status: 200,
92+
description: 'Notifications cleared',
93+
})
94+
@UseGuards(JwtUsersGuard)
95+
@Delete('clear')
96+
async clearUserNotifications(@Request() req: ApiReq): Promise<boolean> {
97+
return await this.notificationsService.userClearNotifications(
98+
req.user._id.toString(),
99+
);
100+
}
101+
102+
@ApiBearerAuth()
103+
@ApiOperation({
104+
summary: 'Get admin notifications dashboard',
105+
description: 'Returns all admin notifications with optional filtering',
106+
})
107+
@ApiQuery({ name: 'status', required: false, enum: ['pending', 'read'] })
108+
@ApiResponse({
109+
status: 200,
110+
description: 'List of admin notifications',
111+
})
112+
@UseGuards(JwtAdminsGuard)
113+
@Get('admin/dashboard')
114+
async getAdminNotifications(
115+
@Query('status') status?: string,
116+
): Promise<any[]> {
117+
return await this.notificationsService.getAdminNotifications(status);
118+
}
119+
120+
@ApiBearerAuth()
121+
@ApiOperation({
122+
summary: 'Mark admin notification as read',
123+
description: 'Marks a specific notification as read for admins',
124+
})
125+
@ApiParam({ name: 'notificationId', type: 'string' })
126+
@ApiResponse({
127+
status: 200,
128+
description: 'Notification marked as read',
129+
})
130+
@UseGuards(JwtAdminsGuard)
131+
@Patch('admin/:notificationId/read')
132+
async markAdminNotificationAsRead(
133+
@Param('notificationId') notificationId: string,
134+
): Promise<boolean> {
135+
return await this.notificationsService.adminReadNotification(
136+
notificationId,
137+
);
138+
}
36139
}

0 commit comments

Comments
 (0)