From bb06783416371a4333bae01b6871e97cbdf883db Mon Sep 17 00:00:00 2001 From: bigKeed <138622264+bigKeed@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:17:31 +0100 Subject: [PATCH] notification fixes --- app.js | 2 +- controllers/authController.js | 2 +- controllers/notificationController.js | 154 +++++++++++++++----------- db/models/notification.js | 12 +- router/notificationRouter.js | 7 +- 5 files changed, 104 insertions(+), 73 deletions(-) diff --git a/app.js b/app.js index 6057c25..98276b7 100644 --- a/app.js +++ b/app.js @@ -41,7 +41,7 @@ app.get( //main-routes app.use('/api/v1/listings', ListingRouter); app.use('/api/v1/marketplace/listings', MarketPlaceRouter); -app.use('/api/v1/notification', notificationRouter); +app.use('/api/v1', notificationRouter); app.use((req, res, next) => { return next( diff --git a/controllers/authController.js b/controllers/authController.js index 696410b..3a2b4dc 100644 --- a/controllers/authController.js +++ b/controllers/authController.js @@ -26,7 +26,7 @@ exports.protect = catchAsync(async (req, res, next) => { const user = await User.findByPk(decoded.user_id); if (!user) return next( - new AppError('The user belonginf to this token does not exist', 401) + new AppError('The user belonging to this token does not exist', 401) ); //check if user is verified if (!user.is_verified) diff --git a/controllers/notificationController.js b/controllers/notificationController.js index 68d7ea7..a095bbc 100644 --- a/controllers/notificationController.js +++ b/controllers/notificationController.js @@ -1,106 +1,136 @@ const AppError = require("../utils/appError"); const catchAsync = require("../utils/catchAsync"); -const { Notification } = require("../db/models"); +const { Notification, User } = require("../db/models"); // Get user notifications with pagination and optional filtering exports.getUserNotifications = catchAsync(async (req, res) => { const { userId } = req.params; - const { page = 1, limit = 20, isRead, type } = req.query; + const { page = 1, limit = 20, is_read, type } = req.query; - // Validate userId - const parsedUserId = parseInt(userId); - if (isNaN(parsedUserId)) { - throw new AppError('Invalid user ID provided', 400); + // Validate userId as UUID + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(userId)) { + throw new AppError("Invalid user ID provided (must be a valid UUID)", 400); } - // Validate pagination params - const parsedPage = Math.max(1, parseInt(page)); - const parsedLimit = Math.min(100, Math.max(1, parseInt(limit))); + // Validate user exists + const user = await User.findByPk(userId); + if (!user) { + throw new AppError("User not found", 404); + } + + // Validate and sanitize pagination params + const parsedPage = Math.max(1, parseInt(page, 10)); + const parsedLimit = Math.min(100, Math.max(1, parseInt(limit, 10))); const offset = (parsedPage - 1) * parsedLimit; if (isNaN(parsedPage)) { - throw new AppError('Invalid page number provided', 400); + throw new AppError("Invalid page number provided", 400); } if (isNaN(parsedLimit)) { - throw new AppError('Invalid limit provided. Must be between 1 and 100', 400); + throw new AppError( + "Invalid limit provided. Must be between 1 and 100", + 400 + ); } // Build where clause with optional filters - const where = { userId: parsedUserId }; - if (isRead !== undefined) { - where.isRead = isRead === 'true'; + const where = { userId }; + if (is_read !== undefined) { + where.is_read = is_read === "true"; } - if (type && ['pickup', 'reward', 'marketplace', 'general'].includes(type)) { + if (type) { + const validTypes = ["pickup", "reward", "marketplace", "general"]; + if (!validTypes.includes(type)) { + throw new AppError( + `Invalid notification type. Must be one of: ${validTypes.join(", ")}`, + 400 + ); + } where.type = type; - } else if (type) { - throw new AppError('Invalid notification type provided', 400); } - - // Query with pagination and filters - const { count, rows: notifications } = await models.Notification.findAndCountAll({ + // Query with pagination, filters, and optimized attributes + const { count, rows: notifications } = await Notification.findAndCountAll({ where, - order: [['createdAt', 'DESC']], + order: [["created_at", "DESC"]], limit: parsedLimit, offset, + attributes: [ + "id", + "type", + "message", + ["is_read", "isRead"], // Map to camelCase in response + ["created_at", "createdAt"], // Map to camelCase in response + ], include: [ { - model: models.User, - as: 'user', - attributes: ['id', 'username'], - required: false, + model: User, + as: "user", + attributes: ["id", "name"], + required: false, }, ], }); + // Calculate pagination metadata const totalPages = Math.ceil(count / parsedLimit); res.status(200).json({ success: true, - notifications, - pagination: { - currentPage: parsedPage, - totalPages, - totalCount: count, - limit: parsedLimit, - hasNextPage: parsedPage < totalPages, - hasPrevPage: parsedPage > 1, + data: { + notifications, + pagination: { + currentPage: parsedPage, + totalPages, + totalCount: count, + limit: parsedLimit, + hasNextPage: parsedPage < totalPages, + hasPrevPage: parsedPage > 1, + }, }, }); }); +exports.sendNotification = catchAsync(async (req, res) => { + const { userId, type, message } = req.body; + if (!userId || !type || !message) { + throw new AppError("Missing required fields: userId, type, message", 400); + } - exports.sendNotification = catchAsync (async (req, res) => { - const { userId, type, message } = req.body; + const validTypes = ["pickup", "reward", "marketplace", "general"]; + if (!validTypes.includes(type)) { + throw new AppError( + `Invalid notification type. Must be one of: ${validTypes.join(", ")}`, + 400 + ); + } - // Validate request body - if (!userId || !type || !message) { - throw new AppError('Missing required fields: userId, type, message', 400); - } + // Validate userId is a valid UUID string (no parseInt) + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(userId)) { + throw new AppError("Invalid user ID provided (must be a valid UUID)", 400); + } - // Validate notification type - const validTypes = ['pickup', 'reward', 'marketplace', 'general']; - if (!validTypes.includes(type)) { - throw new AppError(`Invalid notification type. Must be one of: ${validTypes.join(', ')}`, 400); - } + // Validate user exists + const user = await User.findByPk(userId); + if (!user) { + throw new AppError("User not found", 404); + } - // Validate userId is a number - const parsedUserId = parseInt(userId); - if (isNaN(parsedUserId)) { - throw new AppError('Invalid user ID provided', 400); - } + const notification = await Notification.create({ + userId, + type, + message, + created_at: new Date(), + is_read: false, + }); - const notification = await models.Notification.create({ - userId: parsedUserId, - type, - message, - createdAt: new Date(), - isRead: false, - }); - - res.status(201).json({ - success: true, - notification, - }); - }); \ No newline at end of file + res.status(201).json({ + success: true, + notification, + }); +}); diff --git a/db/models/notification.js b/db/models/notification.js index 2014156..a373b40 100644 --- a/db/models/notification.js +++ b/db/models/notification.js @@ -3,15 +3,15 @@ module.exports = (sequelize, DataTypes) => { const Notification = sequelize.define('Notification', { id: { - type: DataTypes.INTEGER, + type: DataTypes.UUID, primaryKey: true, - autoIncrement: true, + defaultValue: DataTypes.UUIDV4, }, userId: { - type: DataTypes.INTEGER, + type: DataTypes.UUID, allowNull: false, references: { - model: 'users', + model: 'Users', key: 'id', }, onDelete: 'CASCADE', @@ -24,12 +24,12 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.TEXT, allowNull: false, }, - createdAt: { + created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW, }, - isRead: { + is_read: { type: DataTypes.BOOLEAN, defaultValue: false, }, diff --git a/router/notificationRouter.js b/router/notificationRouter.js index 79e9322..667353b 100644 --- a/router/notificationRouter.js +++ b/router/notificationRouter.js @@ -1,9 +1,10 @@ const express = require('express'); const router = express.Router(); const {getUserNotifications, sendNotification} = require('../controllers/notificationController'); +const { protect } = require('../controllers/authController'); - -router.get('/:Id', getUserNotifications); -router.post('/', sendNotification); +// router.use(protect); +router.get('/notifications/:userId', protect, getUserNotifications); +router.post('/notification',protect, sendNotification); module.exports = router; \ No newline at end of file