Skip to content

Commit 4c98f32

Browse files
committed
Completed get calendar view step
1 parent 6cfcfbd commit 4c98f32

12 files changed

Lines changed: 233 additions & 24 deletions

File tree

demo/graph-tutorial/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
OAUTH_APP_ID=YOUR_APP_ID_HERE
22
OAUTH_APP_SECRET=YOUR_APP_SECRET_HERE
33
OAUTH_REDIRECT_URI=http://localhost:3000/auth/callback
4-
OAUTH_SCOPES='user.read,calendars.read'
4+
OAUTH_SCOPES='user.read,calendars.readwrite,mailboxsettings.read'
55
OAUTH_AUTHORITY=https://login.microsoftonline.com/common/

demo/graph-tutorial/app.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require('dotenv').config();
1414
var indexRouter = require('./routes/index');
1515
var usersRouter = require('./routes/users');
1616
var authRouter = require('./routes/auth');
17+
var calendarRouter = require('./routes/calendar');
1718

1819
var app = express();
1920

@@ -86,6 +87,15 @@ app.use(function(req, res, next) {
8687
app.set('views', path.join(__dirname, 'views'));
8788
app.set('view engine', 'hbs');
8889

90+
// <FormatDateSnippet>
91+
var hbs = require('hbs');
92+
var moment = require('moment');
93+
// Helper to format date/time sent by Graph
94+
hbs.registerHelper('eventDateTime', function(dateTime){
95+
return moment(dateTime).format('M/D/YY h:mm A');
96+
});
97+
// </FormatDateSnippet>
98+
8999
app.use(logger('dev'));
90100
app.use(express.json());
91101
app.use(express.urlencoded({ extended: false }));
@@ -94,6 +104,7 @@ app.use(express.static(path.join(__dirname, 'public')));
94104

95105
app.use('/', indexRouter);
96106
app.use('/auth', authRouter);
107+
app.use('/calendar', calendarRouter);
97108
app.use('/users', usersRouter);
98109

99110
// catch 404 and forward to error handler

demo/graph-tutorial/graph.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,32 @@ module.exports = {
1010

1111
const user = await client
1212
.api('/me')
13-
.select('displayName,mail,userPrincipalName')
13+
.select('displayName,mail,mailboxSettings,userPrincipalName')
1414
.get();
1515
return user;
16-
}
16+
},
17+
18+
// <GetCalendarViewSnippet>
19+
getCalendarView: async function(accessToken, start, end, timeZone) {
20+
const client = getAuthenticatedClient(accessToken);
21+
22+
const events = await client
23+
.api('/me/calendarview')
24+
// Add Prefer header to get back times in user's timezone
25+
.header("Prefer", `outlook.timezone="${timeZone}"`)
26+
// Add the begin and end of the calendar window
27+
.query({ startDateTime: start, endDateTime: end })
28+
// Get just the properties used by the app
29+
.select('subject,organizer,start,end')
30+
// Order by start time
31+
.orderby('start/dateTime')
32+
// Get at most 50 results
33+
.top(50)
34+
.get();
35+
36+
return events;
37+
},
38+
// </GetCalendarViewSnippet>
1739
};
1840

1941
function getAuthenticatedClient(accessToken) {

demo/graph-tutorial/package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/graph-tutorial/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
"http-errors": "^1.8.0",
2020
"isomorphic-fetch": "^2.2.1",
2121
"moment": "^2.28.0",
22+
"moment-timezone": "^0.5.31",
2223
"morgan": "^1.10.0",
23-
"uuid": "^8.3.0"
24+
"uuid": "^8.3.0",
25+
"windows-iana": "^4.2.1"
2426
}
2527
}

demo/graph-tutorial/routes/auth.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ router.get('/signin',
2121
console.log(`Error: ${error}`);
2222
req.flash('error_msg', {
2323
message: 'Error getting auth URL',
24-
debug: JSON.stringify(error)
24+
debug: JSON.stringify(error, Object.getOwnPropertyNames(error))
2525
});
2626
res.redirect('/');
2727
}
@@ -49,12 +49,13 @@ router.get('/callback',
4949
// Add the user to user storage
5050
req.app.locals.users[req.session.userId] = {
5151
displayName: user.displayName,
52-
email: user.mail || user.userPrincipalName
52+
email: user.mail || user.userPrincipalName,
53+
timeZone: user.mailboxSettings.timeZone
5354
};
5455
} catch(error) {
5556
req.flash('error_msg', {
5657
message: 'Error completing authentication',
57-
debug: JSON.stringify(error)
58+
debug: JSON.stringify(error, Object.getOwnPropertyNames(error))
5859
});
5960
}
6061

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
const router = require('express-promise-router')();
5+
const graph = require('../graph.js');
6+
const moment = require('moment-timezone');
7+
const iana = require('windows-iana');
8+
9+
/* GET /calendar */
10+
// <GetRouteSnippet>
11+
router.get('/',
12+
async function(req, res) {
13+
if (!req.session.userId) {
14+
// Redirect unauthenticated requests to home page
15+
res.redirect('/')
16+
} else {
17+
const params = {
18+
active: { calendar: true }
19+
};
20+
21+
// Get the user
22+
const user = req.app.locals.users[req.session.userId];
23+
// Convert user's Windows time zone ("Pacific Standard Time")
24+
// to IANA format ("America/Los_Angeles")
25+
// Moment needs IANA format
26+
const timeZoneId = iana.findOneIana(user.timeZone);
27+
console.log(`Time zone: ${timeZoneId.valueOf()}`);
28+
29+
// Calculate the start and end of the current week
30+
// Get midnight on the start of the current week in the user's timezone,
31+
// but in UTC. For example, for Pacific Standard Time, the time value would be
32+
// 07:00:00Z
33+
var startOfWeek = moment.tz(timeZoneId.valueOf()).startOf('week').utc();
34+
var endOfWeek = moment(startOfWeek).add(7, 'day');
35+
console.log(`Start: ${startOfWeek.format()}`);
36+
37+
// Get the access token
38+
var accessToken;
39+
try {
40+
accessToken = await getAccessToken(req.session.userId, req.app.locals.msalClient);
41+
} catch (err) {
42+
req.flash('error_msg', {
43+
message: 'Could not get access token. Try signing out and signing in again.',
44+
debug: JSON.stringify(err, Object.getOwnPropertyNames(err))
45+
});
46+
return;
47+
}
48+
49+
if (accessToken && accessToken.length > 0) {
50+
try {
51+
// Get the events
52+
const events = await graph.getCalendarView(
53+
accessToken,
54+
startOfWeek.format(),
55+
endOfWeek.format(),
56+
user.timeZone);
57+
58+
params.events = events.value;
59+
} catch (err) {
60+
req.flash('error_msg', {
61+
message: 'Could not fetch events',
62+
debug: JSON.stringify(err, Object.getOwnPropertyNames(err))
63+
});
64+
}
65+
}
66+
else {
67+
req.flash('error_msg', 'Could not get an access token');
68+
}
69+
70+
res.render('calendar', params);
71+
}
72+
}
73+
);
74+
// </GetRouteSnippet>
75+
76+
async function getAccessToken(userId, msalClient) {
77+
// Look up the user's account in the cache
78+
const accounts = msalClient
79+
.getTokenCache()
80+
.getAllAccounts();
81+
82+
const userAccount = accounts.find(a => a.homeAccountId === userId);
83+
84+
// Get the token silently
85+
const response = await msalClient.acquireTokenSilent({
86+
scopes: process.env.OAUTH_SCOPES.split(','),
87+
redirectUri: process.env.OAUTH_REDIRECT_URI,
88+
account: userAccount
89+
});
90+
91+
return response.accessToken;
92+
}
93+
94+
module.exports = router;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!-- Copyright (c) Microsoft Corporation.
2+
Licensed under the MIT License. -->
3+
4+
<!-- <LayoutSnippet> -->
5+
<h1>Calendar</h1>
6+
<table class="table">
7+
<thead>
8+
<tr>
9+
<th scope="col">Organizer</th>
10+
<th scope="col">Subject</th>
11+
<th scope="col">Start</th>
12+
<th scope="col">End</th>
13+
</tr>
14+
</thead>
15+
<tbody>
16+
{{#each events}}
17+
<tr>
18+
<td>{{this.organizer.emailAddress.name}}</td>
19+
<td>{{this.subject}}</td>
20+
<td>{{eventDateTime this.start.dateTime}}</td>
21+
<td>{{eventDateTime this.end.dateTime}}</td>
22+
</tr>
23+
{{/each}}
24+
</tbody>
25+
</table>
26+
<!-- </LayoutSnippet> -->

tutorial/02-create-app.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Before moving on, install some additional packages that you will use later:
4242
4343
- [dotenv](https://github.com/motdotla/dotenv) for loading values from a .env file.
4444
- [moment](https://github.com/moment/moment/) for formatting date/time values.
45+
- [windows-iana](https://github.com/rubenillodo/windows-iana) for translating Windows time zone names to IANA time zone IDs.
4546
- [connect-flash](https://github.com/jaredhanson/connect-flash) to flash error messages in the app.
4647
- [express-session](https://github.com/expressjs/session) to store values in an in-memory server-side session.
4748
- [express-promise-router](https://github.com/express-promise-router/express-promise-router) to allow route handlers to return a Promise.
@@ -53,8 +54,8 @@ Before moving on, install some additional packages that you will use later:
5354
1. Run the following command in your CLI.
5455
5556
```Shell
56-
npm install dotenv@8.2.0 moment@2.28.0 connect-flash@0.1.1 express-session@1.17.1 isomorphic-fetch@2.2.1
57-
npm install @azure/msal-node@1.0.0-alpha.5 @microsoft/microsoft-graph-client@2.0.0
57+
npm install dotenv@8.2.0 moment@2.28.0 moment-timezone@0.5.31 connect-flash@0.1.1 express-session@1.17.1 isomorphic-fetch@2.2.1
58+
npm install @azure/msal-node@1.0.0-alpha.5 @microsoft/microsoft-graph-client@2.0.0 windows-iana@4.2.1
5859
```
5960
6061
> [!TIP]

tutorial/04-add-aad-auth.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ In this exercise you will extend the application from the previous exercise to s
4747
console.log(`Error: ${error}`);
4848
req.flash('error_msg', {
4949
message: 'Error getting auth URL',
50-
debug: JSON.stringify(error)
50+
debug: JSON.stringify(error, Object.getOwnPropertyNames(error))
5151
});
5252
res.redirect('/');
5353
}
@@ -75,7 +75,7 @@ In this exercise you will extend the application from the previous exercise to s
7575
} catch (error) {
7676
req.flash('error_msg', {
7777
message: 'Error completing authentication',
78-
debug: JSON.stringify(error)
78+
debug: JSON.stringify(error, Object.getOwnPropertyNames(error))
7979
});
8080
}
8181
@@ -148,10 +148,10 @@ Start the server and browse to `https://localhost:3000`. Click the sign-in butto
148148

149149
const user = await client
150150
.api('/me')
151-
.select('displayName,mail,userPrincipalName')
151+
.select('displayName,mail,mailboxSettings,userPrincipalName')
152152
.get();
153153
return user;
154-
}
154+
},
155155
};
156156

157157
function getAuthenticatedClient(accessToken) {

0 commit comments

Comments
 (0)