Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Booking Facility on Single-Room Page #60

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 9 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# MeetEasier
# MeetEasier - Custom Fork for Raspberry Pi Official Touchscreen

Because why pay money for something you can do yourself?

## Description

## Description
This is a customised fork of "MeetEasier" https://github.com/danxfisher/MeetEasier.
MeetEasier is a web application that visualizes meeting room availability. It works using Exchange Web Services (EWS) with Exchange room lists in Office 365.

![Mockup 1](mockups/mockup-1.jpg)
This particular fork adds additional functionality (e.g. buttons for making bookings, extending bookings and ending meetings) and hides some options depending on environment variables (set in the .env files)

![Mockup 1](mockups/mockup-1.png)

***

Expand All @@ -18,37 +20,7 @@ In the event of wanting to commercially distribute a closed source modification

***

## Updates

* v0.3.4
* [#34](https://github.com/danxfisher/MeetEasier/pull/34) - bug fix for 'Next up:' displaying incorrectly
* v0.3.3
* [#18](https://github.com/danxfisher/MeetEasier/pull/15) - use localized sort for rooms
* v0.3.2
* Added additional error handling for incorrect credentials. The error will now be shown on the front end.
* Updated the socket component to stop most ERR_CONNECTION_REFSUED errors from happening.
* v0.3.1
* Removed skipped rooms/room blacklist filtering from front end and added to back end.
* v0.3
* Cleaned up unnecessarily nested component folder structure
* [#8](https://github.com/danxfisher/MeetEasier/pull/8) - add script-shortcuts to `package.json` in root
* [#9](https://github.com/danxfisher/MeetEasier/pull/9) - support environment-variables for authentication and port configuration
* [#10](https://github.com/danxfisher/MeetEasier/pull/10) - create shrinkwraps for npm-dependencies
* [#11](https://github.com/danxfisher/MeetEasier/pull/11) - add `.editorconfig`
* [#12](https://github.com/danxfisher/MeetEasier/pull/12) - pass error (while fetching appointments), to frontend
* [#13](https://github.com/danxfisher/MeetEasier/pull/13) - set engine-requirements
* [#14](https://github.com/danxfisher/MeetEasier/pull/14) - add heartbeat-endpoint, to check if server is alive (for monitoring)
* [#15](https://github.com/danxfisher/MeetEasier/pull/15) - add '.nvmrc'
* v0.2
* Changed domain to accept more than just ".com" extension
* Changed `ui-react/config/flightboard.config.js` to handle all text so that the application can be multilingual
* Added `ui-react/config/singleRoom.config.js` to do the same for the `single-room` component
* Added `console.log` to `server.js` to know when the server is running correctly
* Updated styles slightly
* v0.1
* Initial release

***

## Assumptions

Expand Down Expand Up @@ -144,7 +116,7 @@ There are three main directories in the `ui-react/src/` folder:

### Simple

* In `/config/auth.js`, enter your credentials and domain:
* In `/config/auth/auth.js`, enter your credentials and domain:

```javascript
module.exports = {
Expand Down Expand Up @@ -219,18 +191,14 @@ There are three main directories in the `ui-react/src/` folder:

## Flightboard Layout Mockup

![Mockup 3](mockups/mockup-3.jpg)
![Mockup 3](mockups/mockup-3.png)

## Single Room Layout Mockup

![Mockup 2](mockups/mockup-2.jpg)
![Mockup 2](mockups/mockup-2.png)

***

## Resources & Attributions

* [ews-javascript-api](https://github.com/gautamsi/ews-javascript-api)
* Mockup Images:
* https://www.anthonyboyd.graphics/mockups/2017/realistic-ipad-pro-mockup-vol-3/
* https://www.freepik.com/free-psd/business-meeting-with-tv-mockup_1163371.htm
* https://www.freepik.com/free-psd/samsung-tv-mockup_800771.htm
97 changes: 97 additions & 0 deletions app/ews/roombooking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module.exports = {
BookRoom: function (roomEmail, roomName, startTime, endTime, bookingType) {
var ews = require("ews-javascript-api");
var auth = require("../../config/auth/auth.js");
var moment = require('moment');
//if NTLM
require('dotenv').config()
//console.log(".env = " + process.env.REACT_APP_EnableNTLM);
if (process.env.REACT_APP_EnableNTLM=="true") {
var ewsNTLM = require("ews-javascript-api-auth");
ews.ConfigurationApi.ConfigureXHR(new ewsNTLM.ntlmAuthXhrApi(auth.exchange.username, auth.exchange.password, true));
}
const exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2013);
exch.Credentials = new ews.WebCredentials(auth.exchange.username, auth.exchange.password);
exch.Url = new ews.Uri(auth.exchange.uri);
//ews.EwsLogging.DebugLogEnabled = true;
if ((bookingType === 'BookNow') || (bookingType === 'BookAfter')){
console.log("BookNow/BookAfter");
var promise = new Promise (function (resolve, reject) {
var appointment = new ews.Appointment(exch);
appointment.Subject = "Booked by Meet-Easier";
appointment.Start = new ews.DateTime(startTime);
appointment.End = new ews.DateTime(endTime);
console.log("RoomBooking: " +appointment.Start + " | " + appointment.End);
appointment.Location = roomName;
appointment.Body = new ews.MessageBody(ews.BodyType.HTML, "Room Booked by Room Panel");
appointment.RequiredAttendees.Add(roomEmail);
let mode = ews.SendInvitationsMode.SendToAllAndSaveCopy
appointment.Save(mode).then(() => {
console.log("Appointment Saved");
}, (ei) => {
console.log(ei.stack, ei.stack.split("\n"));
console.log("error");
});
})
promise.then(function(result){
console.log("SUCCESS");
}, function(err){
console.log(err);
});
}
else if ((bookingType === 'Extend') || (bookingType === 'EndNow')){
var calendarFolderId = new ews.FolderId(ews.WellKnownFolderName.Calendar, new ews.Mailbox(roomEmail));
var view = new ews.CalendarView(ews.DateTime.Now, new ews.DateTime(ews.DateTime.Now.TotalMilliSeconds + 576000000), 6);
exch.FindAppointments(calendarFolderId, view).then((response) => {
var appointments = response.Items;
console.log(bookingType+":START");
appointments.forEach(function(appt, index) {

// get start time from appointment
var start = processTime(appt.Start.momentDate),
end = processTime(appt.End.momentDate),
now = Date.now();

console.log(start);
var apptStartewsDT = new ews.DateTime(new Date(parseInt(start, 10)));
var apptStartTime = moment(start).toISOString();
if (apptStartTime === startTime){
var promise = new Promise (function (resolve, reject) {

appt.End = new ews.DateTime(endTime);
let SIOCmode = ews.SendInvitationsOrCancellationsMode.SendToAllAndSaveCopy;
let CRmode = ews.ConflictResolutionMode.AlwaysOverwrite
appt.Update(CRmode, SIOCmode).then(() => {
console.log("Appointment Saved");
}, (ei) => {
console.log(ei.stack, ei.stack.split("\n"));
console.log("error");
});
})
promise.then(function(result){
console.log(result);
}, function(err){
console.log(err);
});
}


} , (error) => {
// handle the error here
// callback(error, null);
console.log(error);
});
});
}
function processTime(appointmentTime) {
var time = JSON.stringify(appointmentTime);
time = time.replace(/"/g,"");
var time = new Date(time);
var time = time.getTime();

return time;
}
}


}
17 changes: 12 additions & 5 deletions app/ews/roomlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ module.exports = function (callback) {

// modules -------------------------------------------------------------------
var ews = require("ews-javascript-api");
var auth = require("../../config/auth.js");

var auth = require("../../config/auth/auth.js");
//if NTLM
require('dotenv').config()
//console.log(".env = " + process.env.REACT_APP_EnableNTLM);
if (process.env.REACT_APP_EnableNTLM=="true") {
var ewsNTLM = require("ews-javascript-api-auth");
ews.ConfigurationApi.ConfigureXHR(new ewsNTLM.ntlmAuthXhrApi(auth.exchange.username, auth.exchange.password, true));
}
//
// ews -----------------------------------------------------------------------
var exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2016);
exch.Credentials = new ews.ExchangeCredentials(auth.exchange.username, auth.exchange.password);
const exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2013);
exch.Credentials = new ews.WebCredentials(auth.exchange.username, auth.exchange.password);
exch.Url = new ews.Uri(auth.exchange.uri);


// get roomlists from EWS and return sorted array of room list names
exch.GetRoomLists().then((lists) => {
var roomLists = [];

lists.items.forEach(function (item, i, array) {
roomLists.push(item.Name);
});
Expand Down
62 changes: 48 additions & 14 deletions app/ews/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ module.exports = function (callback) {

// modules -------------------------------------------------------------------
var ews = require("ews-javascript-api");
var auth = require("../../config/auth.js");
var auth = require("../../config/auth/auth.js");
var blacklist = require("../../config/room-blacklist.js");

// ews -----------------------------------------------------------------------
var exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2016);
exch.Credentials = new ews.ExchangeCredentials(auth.exchange.username, auth.exchange.password);

//if NTLM
require('dotenv').config()
//console.log(".env = " + process.env.REACT_APP_EnableNTLM);
if (process.env.REACT_APP_EnableNTLM=="true") {
var ewsNTLM = require("ews-javascript-api-auth");
ews.ConfigurationApi.ConfigureXHR(new ewsNTLM.ntlmAuthXhrApi(auth.exchange.username, auth.exchange.password, true));
}
//
const exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2013);
exch.Credentials = new ews.WebCredentials(auth.exchange.username, auth.exchange.password);
exch.Url = new ews.Uri(auth.exchange.uri);


// promise: get all room lists
var getListOfRooms = function () {
var promise = new Promise(function (resolve, reject) {
Expand All @@ -29,7 +35,11 @@ module.exports = function (callback) {
var promise = new Promise(function (resolve, reject) {
var roomAddresses = [];
var counter = 0;

const fs = require('fs');
fs.truncate('./ui-react/build/roomlinks.txt', 0, function(){console.log('roomlinks.txt file cleared')});
fs.truncate('./ui-react/build/roomnames.txt', 0, function(){console.log('roomnames.txt file cleared')});
var ip = require("ip");

roomLists.forEach(function (item, i, array) {
exch.GetRooms(new ews.Mailbox(item.Address)).then((rooms) => {
rooms.forEach(function (roomItem, roomIndex, roomsArray) {
Expand All @@ -39,20 +49,37 @@ module.exports = function (callback) {
// if not in blacklist, proceed as normal; otherwise, skip
if (!inBlacklist) {
let room = {};

// if the email addresses != your corporate domain,
// replace email domain with domain
let email = roomItem.Address;
email = email.substring(0, email.indexOf('@'));
email = email + '@' + auth.domain;

let roomAlias = roomItem.Name.toLowerCase().replace(/\s+/g, "-");


//console.log(roomAlias);
// fs.appendFile('./ui-react/build/roomlinks.txt', 'http://'+ip.address()+':8080/single-room/' + roomAlias + '\r\n', function(err){
fs.appendFile('./ui-react/build/roomlinks.txt', 'http://localhost:8080/single-room/' + roomAlias + '\r\n', function(err){
if(err){
return console.log(err);
}
//console.log("Alias Saved");
});
fs.appendFile('./ui-react/build/roomnames.txt', roomItem.Name.toLowerCase() + '\r\n', function(err){
if(err){
return console.log(err);
}
//console.log("Alias Saved");
});
room.Roomlist = item.Name;
room.Name = roomItem.Name;
room.RoomAlias = roomAlias;
room.Email = email;
roomAddresses.push(room);
//console.log(roomAddresses);
//console.log(room.Roomlist);
//console.log(room.Name);
}
});
counter++;
Expand All @@ -69,20 +96,21 @@ module.exports = function (callback) {

var fillRoomData = function (context, room, appointments = [], option = {}) {
room.Appointments = [];

appointments.forEach(function(appt, index) {

// get start time from appointment
var start = processTime(appt.Start.momentDate),
end = processTime(appt.End.momentDate),
now = Date.now();

room.Busy = index === 0
? start < now && now < end
: room.Busy;

let isAppointmentPrivate = appt.Sensitivity === 'Normal' ? false : true;

let subject = isAppointmentPrivate ? 'Private' : appt.Subject;

console.log("Appointment Subject: " + subject);
room.Appointments.push({
"Subject" : subject,
"Organizer" : appt.Organizer.Name,
Expand Down Expand Up @@ -112,15 +140,21 @@ module.exports = function (callback) {
itemsProcessed: 0,
roomAddresses
};

//console.log(roomAddresses);
roomAddresses.forEach(function(room, index, array){
var calendarFolderId = new ews.FolderId(ews.WellKnownFolderName.Calendar, new ews.Mailbox(room.Email));
var view = new ews.CalendarView(ews.DateTime.Now, new ews.DateTime(ews.DateTime.Now.TotalMilliSeconds + ews.TimeSpan.FromHours(240).asMilliseconds()), 6);
//console.log(calendarFolderId);
var view = new ews.CalendarView(ews.DateTime.Now, new ews.DateTime(ews.DateTime.Now.TotalMilliSeconds + 576000000), 6);


exch.FindAppointments(calendarFolderId, view).then((response) => {
//console.log(room);
//console.log(response.Items);
fillRoomData(context, room, response.Items);
}, (error) => {
// handle the error here
// callback(error, null);
console.log(error);
fillRoomData(context, room, undefined, { errorMessage: error.response.errorMessage });
});
});
Expand Down
22 changes: 19 additions & 3 deletions app/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,26 @@ module.exports = function(app) {
});

// heartbeat-service to check if server is alive
app.get('/api/heartbeat', function(req, res) {
res.json({ status: 'OK' });
});
app.get('/api/heartbeat', function(req, res) {
res.json({ status: 'OK' });
});

// books a room
app.get('/api/roombooking', function(req, res){

console.log("Route Room Booking");
//console.log(req);

var ews = require('./ews/roombooking.js');
var roomEmail = req.query.roomEmail;
var roomName = req.query.roomName;
var startTime = req.query.startTime;
var endTime = req.query.endTime;
var bookingType = req.query.bookingType;
console.log(roomEmail+" | "+roomName+" | "+startTime+" | "+endTime+" | "+bookingType);
ews.BookRoom(roomEmail, roomName, startTime, endTime, bookingType);
res.json({ status: 'Booked' });
});
// redirects everything else to our react app
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname,'../ui-react/build/','index.html'));
Expand Down
2 changes: 1 addition & 1 deletion app/socket-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = function (io){
io.of('/').emit('controllerDone', 'done');
});

setTimeout(callEWS, 60000);
setTimeout(callEWS, 15000);
})();
}

Expand Down
11 changes: 11 additions & 0 deletions config/auth/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// expose our config directly to our application using module.exports
module.exports = {
// this user MUST have full access to all the room accounts
'exchange' : {
'username' : process.env.USERNAME || '[email protected]',
'password' : process.env.PASSWORD || 'PASSWORD',
'uri' : 'https://outlook.office365.com/EWS/Exchange.asmx'
},
// Ex: CONTOSO.COM, Contoso.com, Contoso.co.uk, etc.
'domain' : process.env.DOMAIN || 'DOMAIN.COM'
};
Loading