Skip to content

Commit f26d807

Browse files
committed
feat(inApp-notification): Implement in-app notification feature for new
travel request - [x] Install socket.io and sockect.io-client - [x] Create in-app client - [x] Create sockect io server - [x] Modify new trip request method [Finishes #167891584]
1 parent 9e89d2b commit f26d807

26 files changed

Lines changed: 725 additions & 24 deletions

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"exclude": [
1414
"src/database/",
1515
"src/config/",
16+
"src/helpers/template/",
1617
"src/models/",
1718
"src/helpers/PassportHelper.js",
1819
"src/index.js"
@@ -62,6 +63,8 @@
6263
"sequelize": "^5.15.0",
6364
"sequelize-cli": "^5.5.0",
6465
"sequelize-replace-enum-postgres": "1.5.0",
66+
"socket.io": "^2.2.0",
67+
"socket.io-client": "^2.2.0",
6568
"swagger-ui-express": "^4.0.7",
6669
"underscore": "^1.9.1",
6770
"uuid-validate": "0.0.3"

src/controllers/NotificationOpt.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import jwt from 'jsonwebtoken';
2+
import db from '../database/models';
3+
import Response from '../helpers/Response';
4+
import modifyUserNotificationOpt from '../helpers/modifyUserNotificationOpt';
5+
6+
const { User } = db;
7+
class NotificationOpt {
8+
static async modifyEmailNotificationOption(req, res) {
9+
const { emailOpt } = req.body;
10+
const { payload } = req.payload;
11+
const { email } = payload;
12+
const [rowRetured, updatedUser] = await modifyUserNotificationOpt({ emailOpt }, email);
13+
const opt = emailOpt ? 'on' : 'off';
14+
15+
const response = new Response(
16+
true,
17+
200,
18+
`email notification turned ${opt}successfully`,
19+
{
20+
id: updatedUser.id,
21+
email: updatedUser.email,
22+
emailOpt: updatedUser.emailOpt,
23+
});
24+
25+
return res.status(response.code).send(response);
26+
}
27+
static async modifyInAppNotificationOption(req, res) {
28+
const { inAppOpt } = req.body;
29+
const { payload } = req.payload;
30+
const { email } = payload;
31+
const [rowRetured, updatedUser] = await modifyUserNotificationOpt(
32+
{ inAppOpt },
33+
email
34+
);
35+
const opt = inAppOpt ? "on" : "off";
36+
37+
const response = new Response(
38+
true,
39+
200,
40+
`In-app notification turned ${opt}successfully`,
41+
{
42+
id: updatedUser.id,
43+
email: updatedUser.email,
44+
inAppOpt: updatedUser.inAppOpt
45+
}
46+
);
47+
48+
return res.status(response.code).send(response);
49+
50+
}
51+
}
52+
53+
export default NotificationOpt;

src/controllers/Trip.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import db from '../database/models';
22
import Response from '../helpers/Response';
3+
import SearchDatabase from '../helpers/SearchDatabase';
4+
import EmailNotifications from '../helpers/EmailNotifications';
5+
import addNotification from '../helpers/addNotification';
6+
import inAppBot from '../helpers/inAppBot';
37

48
const {
59
Trip, Branch, User, Stop, Accomodation
@@ -76,13 +80,34 @@ class TripController {
7680
]
7781
}
7882
);
79-
const response = new Response(
80-
true,
81-
201,
82-
'Travel request successfully created',
83-
{ trip }
84-
);
85-
return res.status(response.code).json(response);
83+
if (trip.id) {
84+
const admins = await SearchDatabase.findAdminUsersWithNotificationOpt(
85+
['travel admin', 'manager', 'super admin'], { emailOpt: true }
86+
);
87+
const emails = admins.map(admin => admin.email);
88+
if (emails) {
89+
const data = await SearchDatabase.findTrip(trip.id);
90+
const tripData = {
91+
type,
92+
reason,
93+
departureDate,
94+
returnDate
95+
};
96+
const message = `${data.user.firstName} ${data.user.lastName} requested for a ${type} trip`;
97+
data.trips = { ...data.trips, ...tripData };
98+
EmailNotifications.sendNewTrip(emails, 'New Trip', data);
99+
inAppBot.send({ tripId: trip.id, message });
100+
await addNotification(trip.id, message);
101+
const response = new Response(
102+
true,
103+
201,
104+
'Travel request successfully created',
105+
{ trip }
106+
);
107+
return res.status(response.code).json(response);
108+
}
109+
110+
}
86111
} catch (error) {
87112
const response = new Response(
88113
false,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
module.exports = {
4+
up: (queryInterface, Sequelize) => {
5+
return Promise.all([
6+
queryInterface.addColumn('Users', 'emailOpt', {
7+
type: Sequelize.BOOLEAN,
8+
allowNull: false,
9+
defaultValue: false
10+
}),
11+
queryInterface.addColumn('Users', 'inAppOpt', {
12+
type: Sequelize.BOOLEAN,
13+
allowNull: false,
14+
defaultValue: false
15+
})
16+
]);
17+
},
18+
19+
down: (queryInterface, Sequelize) => {
20+
return Promise.all([
21+
queryInterface.removeColumn('Users', 'emailOpt', {
22+
type: Sequelize.BOOLEAN,
23+
allowNull: true,
24+
defaultValue: false
25+
}),
26+
queryInterface.removeColumn('Users', 'inAppOpt', {
27+
type: Sequelize.BOOLEAN,
28+
allowNull: true,
29+
defaultValue: false
30+
})
31+
]);
32+
}
33+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
module.exports = {
3+
up: (queryInterface, Sequelize) => {
4+
return queryInterface.createTable('Notifications', {
5+
id: {
6+
allowNull: false,
7+
primaryKey: true,
8+
type: Sequelize.DataTypes.UUID,
9+
defaultValue: Sequelize.literal('uuid_generate_v4()'),
10+
},
11+
tripId: {
12+
allowNull: false,
13+
type: Sequelize.UUID,
14+
},
15+
message: {
16+
allowNull: false,
17+
type: Sequelize.STRING
18+
},
19+
createdAt: {
20+
allowNull: false,
21+
type: Sequelize.DATE
22+
},
23+
updatedAt: {
24+
allowNull: false,
25+
type: Sequelize.DATE
26+
}
27+
});
28+
},
29+
down: (queryInterface, Sequelize) => {
30+
return queryInterface.dropTable('Notifications');
31+
}
32+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
module.exports = {
3+
up: (queryInterface, Sequelize) => {
4+
return queryInterface.createTable('Reads', {
5+
id: {
6+
allowNull: false,
7+
autoIncrement: true,
8+
primaryKey: true,
9+
type: Sequelize.INTEGER
10+
},
11+
notificationId: {
12+
type: Sequelize.UUID,
13+
allowNull: false,
14+
onDelete: 'RESTRICT',
15+
onUpdate: 'RESTRICT',
16+
references: {
17+
model: 'Notifications',
18+
key: 'id',
19+
}
20+
},
21+
userId: {
22+
type: Sequelize.UUID,
23+
allowNull: false,
24+
onDelete: 'RESTRICT',
25+
onUpdate: 'RESTRICT',
26+
references: {
27+
model: 'Users',
28+
key: 'id',
29+
}
30+
},
31+
read: {
32+
type: Sequelize.BOOLEAN,
33+
allowNull: false,
34+
},
35+
createdAt: {
36+
allowNull: false,
37+
type: Sequelize.DATE
38+
},
39+
updatedAt: {
40+
allowNull: false,
41+
type: Sequelize.DATE
42+
}
43+
});
44+
},
45+
down: (queryInterface, Sequelize) => {
46+
return queryInterface.dropTable('Reads');
47+
}
48+
};

src/database/models/branch.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ module.exports = (sequelize, DataTypes) => {
1212
});
1313
Branch.hasMany(models.Trip, {
1414
foreignKey: 'startBranchId',
15-
as: 'trip',
15+
as: 'trips',
1616
onDelete: 'CASCADE',
1717
onUpdate: 'CASCADE'
1818
});
1919
Branch.hasMany(models.Accomodation, {
2020
foreignKey: 'branchId',
21-
as: 'accomodation',
21+
as: 'accomodations',
2222
onDelete: 'CASCADE',
2323
onUpdate: 'CASCADE'
2424
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
module.exports = (sequelize, DataTypes) => {
3+
const Notification = sequelize.define('Notification', {
4+
message: DataTypes.STRING,
5+
tripId: DataTypes.UUID
6+
}, {});
7+
Notification.associate = function(models) {
8+
// associations can be defined here
9+
};
10+
return Notification;
11+
};

src/database/models/read.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
module.exports = (sequelize, DataTypes) => {
3+
const Read = sequelize.define('Read', {
4+
notificationId: DataTypes.UUID,
5+
userId: DataTypes.UUID,
6+
read: DataTypes.BOOLEAN
7+
}, {});
8+
Read.associate = function(models) {
9+
// associations can be defined here
10+
Read.belongsTo(models.Notification, {
11+
foreignKey: 'notificationId',
12+
as: 'notifications',
13+
onDelete: 'CASCADE',
14+
onUpdate: 'CASCADE'
15+
});
16+
Read.belongsTo(models.Notification, {
17+
foreignKey: 'userId',
18+
as: 'users',
19+
onDelete: 'CASCADE',
20+
onUpdate: 'CASCADE'
21+
});
22+
};
23+
return Read;
24+
};

src/database/models/user.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ module.exports = (sequelize, DataTypes) => {
1111
role: DataTypes.ENUM('super admin', 'travel admin', 'travel team member', 'manager', 'requester', 'staff'),
1212
status: DataTypes.ENUM('active', 'inactive', 'unverified'),
1313
companyId: DataTypes.UUID,
14-
favorites: DataTypes.BOOLEAN
14+
favorites: DataTypes.BOOLEAN,
15+
emailOpt: DataTypes.BOOLEAN,
16+
inAppOpt: DataTypes.BOOLEAN
1517
},
1618
{}
1719
);

0 commit comments

Comments
 (0)