diff --git a/src/controllers/notificationsController.ts b/src/controllers/notificationsController.ts new file mode 100644 index 00000000..515adfbc --- /dev/null +++ b/src/controllers/notificationsController.ts @@ -0,0 +1,45 @@ +import { Request, Response } from 'express'; +import Notification from '../database/models/notification'; +import { sendInternalErrorResponse } from '../validations'; +import logger from '../logs/config'; + +export const getNotifications = async (req: Request, res: Response): Promise => { + try { + const { userId } = req.params; + const notifications = await Notification.findAll({ where: { userId } }); + if (notifications.length === 0) { + logger.error('No notifications were found'); + res.status(404).json({ + ok: false, + message: 'No notifications that were found', + }); + return; + } + res.status(200).json({ ok: true, data: notifications }); + } catch (error) { + logger.error(error); + sendInternalErrorResponse(res, error); + return; + } +}; + +export const markNotificationAsRead = async (req: Request, res: Response): Promise => { + const { id } = req.params; + const { isRead } = req.body; + try { + const notification = await Notification.findByPk(id); + if (!notification) { + res.status(401).json({ + ok: false, + errorMessage: 'No such notification that were found! Try again', + }); + return; + } + notification.isRead = isRead; + await notification.save(); + res.status(200).json({ ok: true, message: 'Notification were updated successfully' }); + } catch (error) { + logger.error(error); + sendInternalErrorResponse(res, error); + } +}; diff --git a/src/controllers/productsController.ts b/src/controllers/productsController.ts index 9c77c308..a1cb4805 100644 --- a/src/controllers/productsController.ts +++ b/src/controllers/productsController.ts @@ -8,6 +8,9 @@ import { Product, ProductAttributes } from '../database/models/Product'; import { Size, SizeAttributes } from '../database/models/Size'; import logger from '../logs/config'; import sequelize from '../database/models'; +import Notification from '../database/models/notification'; +import User from '../database/models/user'; +import { sendEmail } from '../helpers/send-email'; export const createProduct = async (req: Request, res: Response) => { try { @@ -254,3 +257,17 @@ export const deleteProductById = async (req: Request, res: Response) => { sendInternalErrorResponse(res, error); } }; +//HOOKS TO MANAGE NOTIFICATIONS AFTER OPERATIONS ARE MADE ON PRODUCT + +Product.afterCreate(async product => { + const notification = await Notification.create({ + message: `Product called: ${product.name} was created successfully!`, + isRead: false, + userId: product.sellerId, + }); + const user = await User.findOne({ where: { id: product.sellerId }, attributes: ['email', 'firstName', 'lastName'] }); + if (!user) { + return Promise.reject(new Error("User cannot be found! So the email won't be send successfully")); + } + sendEmail('added_product_notification', { email: user.email, name: user.firstName }); +}); diff --git a/src/database/migrations/20240507222727-create-notification.js b/src/database/migrations/20240507222727-create-notification.js new file mode 100644 index 00000000..7b088b2e --- /dev/null +++ b/src/database/migrations/20240507222727-create-notification.js @@ -0,0 +1,46 @@ +'use strict'; + +const { UUIDV4 } = require('sequelize'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('Notifications', { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + }, + message: { + type: Sequelize.STRING, + allowNull: false, + }, + isRead: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + userId: { + type: Sequelize.UUID, + allowNull: false, + references: { + model: 'Users', + key: 'id', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('Notifications'); + }, +}; diff --git a/src/database/models/notification.ts b/src/database/models/notification.ts new file mode 100644 index 00000000..a1e6f4f2 --- /dev/null +++ b/src/database/models/notification.ts @@ -0,0 +1,55 @@ +import { Model, Optional, DataTypes, UUIDV4 } from 'sequelize'; +import sequelize from './index'; + +export interface NotificationAttributes { + id: string; + userId: string; + message: string; + isRead: boolean; +} + +export interface NotificationCreationAttributes extends Optional {} + +class Notification extends Model { + public id!: string; + public message!: string; + public userId!: string; + public isRead!: boolean; + public readonly createdAt: Date | undefined; + public readonly updatedAt: Date | undefined; +} +Notification.init( + { + id: { + type: DataTypes.UUID, + defaultValue: UUIDV4, + primaryKey: true, + allowNull: false, + unique: true, + }, + message: { + type: DataTypes.STRING, + allowNull: false, + }, + isRead: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'Users', + key: 'id', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }, + }, + { + sequelize: sequelize, + timestamps: true, + } +); + +export default Notification; diff --git a/src/docs/products.yaml b/src/docs/products.yaml index 3f5daebd..b754dbb2 100644 --- a/src/docs/products.yaml +++ b/src/docs/products.yaml @@ -203,6 +203,7 @@ paths: message: type: string example: Something went wrong when creating the product +<<<<<<< HEAD /api/products/{sizeId}/available: put: @@ -308,6 +309,26 @@ paths: 500: description: Internal server error +======= + /api/notifications/{userId}: + get: + tags: + - Product + summary: Fetching all notifications for product creation + description: This get all notifications that are also related to certain product lifecycle change and also related to the user. + security: + - bearerAuth: [] + parameters: + - in: path + name: userId + required: true + type: string + responses: + 404: + description: Notifications were not found! Please try again + 200: + description: Notifications were successfully fetched +>>>>>>> 8a7df7b (Creating notifications) components: securitySchemes: bearerAuth: diff --git a/src/helpers/send-email.ts b/src/helpers/send-email.ts index 2632fb63..95d162b9 100644 --- a/src/helpers/send-email.ts +++ b/src/helpers/send-email.ts @@ -222,6 +222,25 @@ export const sendEmail = async (type: string, data: IData) => { mailOptions.subject = 'Verification code'; mailOptions.html = emailContent; break; + case 'added_product_notification': + email = { + body: { + name: data.name, + intro: `Your product has been added successfully!`, + action: { + instructions: 'To view your product on platform, Click here:', + button: { + color: '#22BC66', + text: 'View on the platform', + link: 'https://e-commerce-mavericks.com/login', + }, + }, + outro: 'Thank you for working with us. If you need any help, please free to contact us!', + }, + }; + mailOptions.subject = 'Product added successfully'; + mailOptions.html = mailGenerator.generate(email); + break; } const info = await transporter.sendMail(mailOptions); logger.info('Send Mailer', info); diff --git a/src/routes/index.ts b/src/routes/index.ts index 6408f11e..a474205b 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -7,7 +7,7 @@ import roleRoute from './roleRoute'; import productRouter from './productRoutes'; import { categoryRouter } from './categoryRouter'; import wishlistRoute from './wishlistRoute'; - +import notificationRoutes from './notificationRoutes'; const router = Router(); router.use('/users', userRoute); @@ -18,4 +18,5 @@ router.use('/roles', roleRoute); router.use('/products', productRouter); router.use('/category', categoryRouter); router.use('/wishlist', wishlistRoute); +router.use('/notifications', notificationRoutes); export default router; diff --git a/src/routes/notificationRoutes.ts b/src/routes/notificationRoutes.ts new file mode 100644 index 00000000..48bf9ef7 --- /dev/null +++ b/src/routes/notificationRoutes.ts @@ -0,0 +1,9 @@ +import express from 'express'; +import { getNotifications, markNotificationAsRead } from '../controllers/notificationsController'; + +const route = express.Router(); + +route.get('/:userId', getNotifications); +route.patch('/:id', markNotificationAsRead); + +export default route;