Skip to content

Commit

Permalink
Merge pull request #16 from Adoptaunpeludo/notifications_pagination
Browse files Browse the repository at this point in the history
Notifications pagination
  • Loading branch information
JoseAlbDR authored Feb 26, 2024
2 parents 95e05d4 + 2ec85f9 commit 6d8339a
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 28 deletions.
12 changes: 12 additions & 0 deletions prisma/migrations/20240226163703_is_read_is_read_at/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `readed` on the `Notification` table. All the data in the column will be lost.
- You are about to drop the column `readedAt` on the `Notification` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Notification" DROP COLUMN "readed",
DROP COLUMN "readedAt",
ADD COLUMN "isRead" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "isReaddAt" TIMESTAMP(3);
9 changes: 9 additions & 0 deletions prisma/migrations/20240226165313_fix_is_read_at/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
Warnings:
- You are about to drop the column `isReaddAt` on the `Notification` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Notification" DROP COLUMN "isReaddAt",
ADD COLUMN "isReadAt" TIMESTAMP(3);
5 changes: 2 additions & 3 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ model Notification {
id String @id @default(uuid())
userId String
message String
readed Boolean @default(false)
isRead Boolean @default(false)
createdAt DateTime @default(now())
readedAt DateTime?
isReadAt DateTime?
queue String
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
Expand Down Expand Up @@ -78,7 +78,6 @@ model Admin {
model ContactInfo {
id String @id
phoneNumber String
address String
cityId Int
city City @relation(fields: [cityId], references: [id])
user User @relation(fields: [id], references: [id], onDelete: Cascade)
Expand Down
9 changes: 3 additions & 6 deletions src/presentation/animals/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
PaginationDto,
UpdateAnimalDto,
} from '../../domain';
import { ProducerService, S3Service } from '../common/services';
import { QueueService, S3Service } from '../common/services';

export class AnimalRoutes {
static get routes() {
Expand All @@ -30,11 +30,8 @@ export class AnimalRoutes {
envs.AWS_SECRET_ACCESS_KEY,
envs.AWS_BUCKET
);
const emailService = new ProducerService(
envs.RABBITMQ_URL,
'email-request'
);
const notificationService = new ProducerService(
const emailService = new QueueService(envs.RABBITMQ_URL, 'email-request');
const notificationService = new QueueService(
envs.RABBITMQ_URL,
'notification-request'
);
Expand Down
7 changes: 2 additions & 5 deletions src/presentation/auth/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Router } from 'express';

import { AuthController } from './controller';
import { AuthMiddleware, ValidationMiddleware } from '../middlewares';
import { ProducerService } from '../common/services';
import { QueueService } from '../common/services';
import { AuthService } from './service';
import { JWTAdapter, envs } from '../../config';
import {
Expand All @@ -18,10 +18,7 @@ export class AuthRoutes {

const jwt = new JWTAdapter(envs.JWT_SEED);

const emailService = new ProducerService(
envs.RABBITMQ_URL,
'email-request'
);
const emailService = new QueueService(envs.RABBITMQ_URL, 'email-request');

const authService = new AuthService(jwt, emailService);
const authController = new AuthController(authService);
Expand Down
15 changes: 14 additions & 1 deletion src/presentation/users/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,23 @@ export class UserController {
* Retrieves user's notifications.
*/
getUserNotifications = async (req: Request, res: Response) => {
const { limit = 5, page = 1 } = req.query;
const user = req.user;

const notifications = await this.userService.getNotifications(user.id!);
const notifications = await this.userService.getNotifications(user.id!, {
limit: +limit,
page: +page,
});

res.status(HttpCodes.OK).json(notifications);
};

readNotification = async (req: Request, res: Response) => {
const { id } = req.params;
const user = req.user;

await this.userService.readNotification(user, id);

res.status(HttpCodes.OK).json({ message: 'Notification marked as read' });
};
}
23 changes: 16 additions & 7 deletions src/presentation/users/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
UpdatePasswordDto,
UpdateSocialMediaDto,
} from '../../domain';
import { S3Service, ProducerService } from '../common/services';
import { S3Service, QueueService } from '../common/services';
import { UserService } from './service';

export class UserRoutes {
Expand All @@ -28,7 +28,7 @@ export class UserRoutes {
envs.AWS_SECRET_ACCESS_KEY,
envs.AWS_BUCKET
);
const notificationService = new ProducerService(
const notificationService = new QueueService(
envs.RABBITMQ_URL,
'notification-request'
);
Expand All @@ -38,22 +38,28 @@ export class UserRoutes {

router.use(authMiddleware.authenticateUser);

router.get('/me', userController.getCurrentUser);

// Favorites
router.get(
'/me/favorites',
// authMiddleware.authorizePermissions('adopter'),
authMiddleware.authorizePermissions('adopter'),
userController.getUserFavorites
);

// Notifications
router.get('/me/notifications', userController.getUserNotifications);

router.put('/me/notifications/read/:id', userController.readNotification);

// Animals
router.get(
'/me/animals/',
// authMiddleware.authorizePermissions('shelter'),
authMiddleware.authorizePermissions('shelter'),
userController.getUserAnimals
);

// Current User CRUD
router.get('/me', userController.getCurrentUser);

router.put(
'/me',
ValidationMiddleware.validate(UpdateUserDto),
Expand All @@ -78,13 +84,16 @@ export class UserRoutes {
router.delete('/me', userController.deleteUser);

router.get('/:id', userController.getSingleUser);
// End Current User CRUD

// All users
router.get(
'/',
// authMiddleware.authorizePermissions('admin'),
authMiddleware.authorizePermissions('admin'),
userController.getAllUsers
);

// Images
router.post(
'/upload-images',
[
Expand Down
76 changes: 71 additions & 5 deletions src/presentation/users/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,15 +599,81 @@ export class UserService {
/**
* Fetches notifications for a user.
* @param id - ID of the user.
* @returns Array of notification objects.
* @param paginationDto - DTO containing pagination parameters.
* @returns Object containing paginated list of user's notifications.
*/
public async getNotifications(id: string) {
const notifications = await prisma.notification.findMany({
public async getNotifications(id: string, paginationDto: PaginationDto) {
const { limit = 5, page = 1 } = paginationDto;

const [total, notifications] = await prisma.$transaction([
prisma.notification.count({ where: { userId: id } }),
prisma.notification.findMany({
skip: (page - 1) * limit,
take: limit,
where: {
userId: id,
},
}),
]);

const maxPages = Math.ceil(total / limit);

return {
currentPage: page,
maxPages,
limit,
total,
next:
page + 1 <= maxPages
? `/api/users/me/notifications?page=${page + 1}&limit=${limit}`
: null,
prev:
page - 1 > 0
? `/api/users/me/notifications?page=${page - 1}&limit=${limit}`
: null,
notifications,
};
}

/**
* Marks a notification as read for a user.
* @param user - PayloadUser object representing the user.
* @param id - ID of the notification to mark as read or 'all' to mark all notifications as read
*/
public async readNotification(user: PayloadUser, id: string) {
if (id === 'all')
await prisma.notification.updateMany({
where: {
userId: user.id,
},
data: {
isRead: true,
isReadAt: new Date(),
},
});

const notification = await prisma.notification.findUnique({
where: {
userId: id,
id,
},
});

if (!notification) throw new NotFoundError('Notification not found');
if (notification.isRead)
throw new BadRequestError('Notification is already read');

CheckPermissions.check(user, notification.userId);

await prisma.notification.update({
where: {
id: id,
},
data: {
isRead: true,
isReadAt: new Date(),
},
});

return notifications;
return true;
}
}
1 change: 0 additions & 1 deletion src/test/presentation/animals/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { TestUser, cleanDB } from '../auth/routes.test';
import request from 'supertest';
import { CreateAnimalDto } from '../../../domain';
import { gender } from '../../../domain/interfaces';
import { S3Service } from '../../../presentation/services';

jest.mock('../../../presentation/services/s3.service.ts');

Expand Down

0 comments on commit 6d8339a

Please sign in to comment.