Skip to content

Commit

Permalink
ft(booking and travel notifications):add a userId column in the accom…
Browse files Browse the repository at this point in the history
…modation table

refactor the existing notification code to ensure reusability
Add notification functionality to the trip and booking controller.
[Starts #169258639]
  • Loading branch information
hezzie committed Dec 12, 2019
1 parent bc1c126 commit 9ff9505
Show file tree
Hide file tree
Showing 33 changed files with 720 additions and 161 deletions.
3 changes: 3 additions & 0 deletions src/controllers/BookingController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Response from '../helpers/Response';
import AccommodationService from '../services/BookingService';
import NotificationService from '../services/NotificationService';

/**
* @exports
Expand All @@ -18,6 +19,8 @@ class BookingController {
static async bookAccommodation(req, res) {
try {
const result = await AccommodationService.createAccommodationService(req);
req.result = result;
await NotificationService.newBookingNotification(req);
return Response.successMessage(req, res, 'You have booked an accommodation facility successfully', result, 201);
} catch (error) {
return Response.errorMessage(req, res, 'Server error', 500);
Expand Down
24 changes: 24 additions & 0 deletions src/controllers/LocationController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dotenv from 'dotenv';
import Response from '../helpers/Response';
import { cities } from '../database/models';
import LocationService from '../services/LocationService';

dotenv.config();
/**
Expand All @@ -27,5 +28,28 @@ class LocationController {
return Response.errorMessage(req, res, err.message, 500);
}
}

/**
* An admin should be able to add locations
* @static
* @param {object} req request object
* @param {object} res response object
* @memberof LocationController
* @returns {object} data
*/
static async getAllCities(req, res) {
try {
const result = await LocationService.getLocations();
if (result.length === 0) {
return Response.errorMessage(req, res, 'No city is available at the moment', 404);
}
return Response.successMessage(req, res, 'Location retrieved','', 200);

} catch (err) {
console.log(err);

return Response.errorMessage(req, res, err.message, 500);
}
}
}
export default LocationController;
36 changes: 36 additions & 0 deletions src/controllers/NotificationController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

import dotenv from 'dotenv';
import Response from '../helpers/Response';
import NotificationService from '../services/NotificationService';

const { markNotificationAsRead } = NotificationService;
dotenv.config();
/**
* @exports
* @class AccommodationController
*/
class AccommodationController {
/**
* Travel Admin can be able to create accommodation facility
* @static
* @param {object} req request object
* @param {object} res response object
* @memberof AccommodationController
* @returns {object} data
*/
static async markNotificationsAsRead(req, res) {
try {
const { notificationIds } = req.body;
const { notificationId } = req.params;
const notificationArr = req.params.notificationId
? notificationIds.filter(id => id === parseInt(notificationId, 10))
: notificationIds;
await markNotificationAsRead(notificationArr);
return Response.successMessage(req, res, 'Notification marked as read successfully', '', 201);
} catch (err) {
return Response.errorMessage(req, res, 'Server Error', 500);
}
}
}

export default AccommodationController;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.describeTable('accommodations').then(tableDefinition => {
if (!tableDefinition['userId']){
return queryInterface.addColumn('accommodations', 'userId', {
type: Sequelize.INTEGER,
allowNull: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'users',
key: 'id'
} } );
} else {
return Promise.resolve(true);
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('accommodations');
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.describeTable('notifications').then(tableDefinition => {
if (!tableDefinition['bookingId']){
return queryInterface.addColumn('notifications', 'bookingId', {
type: Sequelize.INTEGER,
allowNull: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'bookings',
key: 'id'
} } );
} else {
return Promise.resolve(true);
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('notifications');
}
}


5 changes: 5 additions & 0 deletions src/database/models/accommodations.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = (sequelize, DataTypes) => {
const accommodations = sequelize.define('accommodations', {
userId: DataTypes.INTEGER,
name: DataTypes.STRING,
userId: DataTypes.INTEGER,
cityId: DataTypes.INTEGER,
address: DataTypes.STRING,
description: DataTypes.STRING,
Expand All @@ -27,6 +28,10 @@ module.exports = (sequelize, DataTypes) => {
sourceKey: "cityId",
targetKey: "id",
});
accommodations.belongsTo(models.users, {
sourceKey: "userId",
targetKey: "id",
});
accommodations.hasMany(models.ratings, {
targetKey: "accommodationId",
sourceKey: "id",
Expand Down
6 changes: 6 additions & 0 deletions src/database/models/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
module.exports = (sequelize, DataTypes) => {
const notifications = sequelize.define('notifications', {
userId: DataTypes.INTEGER,
bookingId:DataTypes.INTEGER,
managerId: DataTypes.INTEGER,
tripRequestId: DataTypes.INTEGER,
message: DataTypes.TEXT,
Expand All @@ -18,6 +19,11 @@ module.exports = (sequelize, DataTypes) => {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
notifications.belongsTo(models.booking, {
sourceKey:'bookingId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
};
return notifications;
};
9 changes: 8 additions & 1 deletion src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ module.exports = (sequelize, DataTypes) => {
{onUpdate: 'cascade'}
);
users.hasMany(models.notifications,
{targetKey: 'id'},
{sourceKey: 'id'},
{targetKey: 'userId'},
{onDelete: 'cascade'},
{onUpdate: 'cascade'}
);
users.hasMany(models.accommodations,
{sourceKey:'id'},
{targetKey: 'userId'},
{onDelete: 'cascade'},
{onUpdate: 'cascade'}
);
};
return users;
};
33 changes: 33 additions & 0 deletions src/database/seeders/20191105144530-accommodations.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
address: 'kigali',
description: '5 stars hotels...',
googleCoordinates: '-1.956173, 30.063451',

createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -18,6 +19,7 @@ module.exports = {
address: 'kigali',
description: '6 stars hotels...',
googleCoordinates: '-1.953656, 30.062354',

createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -27,9 +29,40 @@ module.exports = {
address: 'kigali',
description: '7 stars hotels...',
googleCoordinates: '-1.953656, 30.062354',

createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'Golden keys hotel',
description: 'Best place to be',
cityId: 1,
googleCoordinates: 'yeaaaahaaa',

address: 'qwerty',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'radisson blue hotel',
description: 'Best place to rest',
cityId: 3,
googleCoordinates: 'yeaaaahaaasdddds',

address: 'qwertysssds',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'Akagera safe motel',
cityId: 2,
address: 'Kigali',
description: 'Pool',
googleCoordinates: '111.45, 456.34',

createdAt: new Date(),
updatedAt: new Date()
}

], {});
},
Expand Down
30 changes: 0 additions & 30 deletions src/database/seeders/20191203191136-accommodation.js

This file was deleted.

23 changes: 0 additions & 23 deletions src/database/seeders/20191203204612-accommodations.js

This file was deleted.

7 changes: 7 additions & 0 deletions src/helpers/TripHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Response from './Response';
import emailHelper from './EmailHelper';
import CommonQueries from '../services/CommonQueries';
import { tripRequests, trips, cities } from '../database/models';
import NotificationService from '../services/NotificationService';


/**
Expand All @@ -20,6 +21,9 @@ class TripHelper {
*/
static async createNewTrip(req, res, tripTypeId) {
try {
if (tripTypeId === 1) {
req.body.returnDate = null;
}
const itinerary = req.body.itinerary ? req.body.itinerary : [req.body];
const userId = req.user.id;
const newTrip = await tripRequests.create({
Expand All @@ -35,6 +39,9 @@ class TripHelper {
returnDate: item.returnDate
});
});

req.result = newTrip;
await NotificationService.newTripRequestNotification(req);
emailHelper.approveEmailHelper(req, process.env.MANAGER_EMAIL);
return Response.successMessage(req, res, 'Trip requested successfully', newTrip, 201);
} catch (err) {
Expand Down
18 changes: 18 additions & 0 deletions src/helpers/notificationConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import eventEmitters from './eventEmitters';

const notificationEvents = (eventName, clientData) => {
eventEmitters.emit(eventName, JSON.stringify(clientData));
};

const sendNotification = (io, emitterEvent, socketEvent) => {
io.on('connection', (socket) => {
eventEmitters.on(emitterEvent, (data) => {
socket.emit(socketEvent, data);
});
});
};

export {
notificationEvents,
sendNotification
};
11 changes: 5 additions & 6 deletions src/helpers/socketIo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import socketIO from 'socket.io';
import eventEmitters from './eventEmitters';
import { sendNotification } from './notificationConfig';

const socketIo = (server) => {
const io = socketIO(server);
Expand All @@ -12,11 +12,10 @@ const socketIo = (server) => {
}
});

io.on('connection', (socket) => {
eventEmitters.on('notification_message', (data) => {
socket.emit('approve_reject', data);
});
});
sendNotification(io, 'approve_reject_notification', 'approve_reject_client');
sendNotification(io, 'booking_notification', 'booking_client');
sendNotification(io, 'trip_request_notification', 'trip_request_client');

return io;
};

Expand Down
13 changes: 13 additions & 0 deletions src/middlewares/Validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,5 +311,18 @@ class Validate {
check('rooms.*.roomType', 'roomType should be a minimum of 2 letters').isString().isLength({ min: 2 })
];
}

/**
* Validate notification id
* @static
* @returns {object} errors
*/
static validateNotificationIdRules() {
return [
check('notificationId', 'The notificationId should be an integer').isInt().optional(),
check('notificationIds', 'The notificationIds should be an array').isArray(),
check('notificationIds.*', 'The notification IDs should be integer').isInt()
];
}
}
export default Validate;
Loading

0 comments on commit 9ff9505

Please sign in to comment.