Skip to content

Commit 880477b

Browse files
committed
Completed create event step
1 parent e694d9d commit 880477b

8 files changed

Lines changed: 236 additions & 2 deletions

File tree

demo/graph-tutorial/graph.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,47 @@ module.exports = {
3636
return events;
3737
},
3838
// </GetCalendarViewSnippet>
39+
40+
// <CreateEventSnippet>
41+
createEvent: async function(accessToken, formData, timeZone) {
42+
const client = getAuthenticatedClient(accessToken);
43+
44+
// Build a Graph event
45+
const newEvent = {
46+
subject: formData.subject,
47+
start: {
48+
dateTime: formData.start,
49+
timeZone: timeZone
50+
},
51+
end: {
52+
dateTime: formData.end,
53+
timeZone: timeZone
54+
},
55+
body: {
56+
contentType: 'text',
57+
content: formData.body
58+
}
59+
};
60+
61+
// Add attendees if present
62+
if (formData.attendees) {
63+
newEvent.attendees = [];
64+
formData.attendees.forEach(attendee => {
65+
newEvent.attendees.push({
66+
type: 'required',
67+
emailAddress: {
68+
address: attendee
69+
}
70+
});
71+
});
72+
}
73+
74+
// POST /me/events
75+
await client
76+
.api('/me/events')
77+
.post(newEvent);
78+
}
79+
// </CreateEventSnippet>
3980
};
4081

4182
function getAuthenticatedClient(accessToken) {

demo/graph-tutorial/package-lock.json

Lines changed: 19 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"express": "^4.17.1",
1616
"express-promise-router": "^4.0.1",
1717
"express-session": "^1.17.1",
18+
"express-validator": "^6.6.1",
1819
"hbs": "^4.1.1",
1920
"http-errors": "^1.8.0",
2021
"isomorphic-fetch": "^2.2.1",

demo/graph-tutorial/routes/calendar.js

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ const router = require('express-promise-router')();
55
const graph = require('../graph.js');
66
const moment = require('moment-timezone');
77
const iana = require('windows-iana');
8+
const { body, validationResult } = require('express-validator');
9+
const validator = require('validator');
810

9-
/* GET /calendar */
1011
// <GetRouteSnippet>
12+
/* GET /calendar */
1113
router.get('/',
1214
async function(req, res) {
1315
if (!req.session.userId) {
@@ -73,6 +75,108 @@ router.get('/',
7375
);
7476
// </GetRouteSnippet>
7577

78+
// <GetEventFormSnippet>
79+
/* GET /calendar/new */
80+
router.get('/new',
81+
function(req, res) {
82+
if (!req.session.userId) {
83+
// Redirect unauthenticated requests to home page
84+
res.redirect('/')
85+
} else {
86+
res.locals.newEvent = {};
87+
res.render('newevent');
88+
}
89+
}
90+
);
91+
// </GetEventFormSnippet>
92+
93+
// <PostEventFormSnippet>
94+
/* POST /calendar/new */
95+
router.post('/new', [
96+
body('ev-subject').escape(),
97+
// Custom sanitizer converts ;-delimited string
98+
// to an array of strings
99+
body('ev-attendees').customSanitizer(value => {
100+
return value.split(';');
101+
// Custom validator to make sure each
102+
// entry is an email address
103+
}).custom(value => {
104+
value.forEach(element => {
105+
if (!validator.isEmail(element)) {
106+
throw new Error('Invalid email address');
107+
}
108+
});
109+
110+
return true;
111+
}),
112+
// Ensure start and end are ISO 8601 date-time values
113+
body('ev-start').isISO8601(),
114+
body('ev-end').isISO8601(),
115+
body('ev-body').escape()
116+
], async function(req, res) {
117+
if (!req.session.userId) {
118+
// Redirect unauthenticated requests to home page
119+
res.redirect('/')
120+
} else {
121+
// Build an object from the form values
122+
const formData = {
123+
subject: req.body['ev-subject'],
124+
attendees: req.body['ev-attendees'],
125+
start: req.body['ev-start'],
126+
end: req.body['ev-end'],
127+
body: req.body['ev-body']
128+
};
129+
130+
// Check if there are any errors with the form values
131+
const formErrors = validationResult(req);
132+
if (!formErrors.isEmpty()) {
133+
134+
let invalidFields = '';
135+
formErrors.errors.forEach(error => {
136+
invalidFields += `${error.param.slice(3, error.param.length)},`
137+
});
138+
139+
// Preserve the user's input when re-rendering the form
140+
// Convert the attendees array back to a string
141+
formData.attendees = formData.attendees.join(';');
142+
return res.render('newevent', {
143+
newEvent: formData,
144+
error: [{ message: `Invalid input in the following fields: ${invalidFields}` }]
145+
});
146+
}
147+
148+
// Get the access token
149+
var accessToken;
150+
try {
151+
accessToken = await getAccessToken(req.session.userId, req.app.locals.msalClient);
152+
} catch (err) {
153+
req.flash('error_msg', {
154+
message: 'Could not get access token. Try signing out and signing in again.',
155+
debug: JSON.stringify(err, Object.getOwnPropertyNames(err))
156+
});
157+
return;
158+
}
159+
160+
// Get the user
161+
const user = req.app.locals.users[req.session.userId];
162+
163+
// Create the event
164+
try {
165+
await graph.createEvent(accessToken, formData, user.timeZone);
166+
} catch (error) {
167+
req.flash('error_msg', {
168+
message: 'Could not create event',
169+
debug: JSON.stringify(error, Object.getOwnPropertyNames(error))
170+
});
171+
}
172+
173+
// Redirect back to the calendar view
174+
return res.redirect('/calendar');
175+
}
176+
}
177+
);
178+
// </PostEventFormSnippet>
179+
76180
async function getAccessToken(userId, msalClient) {
77181
// Look up the user's account in the cache
78182
const accounts = msalClient
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!-- Copyright (c) Microsoft Corporation.
2+
Licensed under the MIT License. -->
3+
4+
<!-- <NewEventFormSnippet> -->
5+
<form method="POST">
6+
<div class="form-group">
7+
<label>Subject</label>
8+
<input class="form-control" name="ev-subject" type="text" value="{{ newEvent.subject }}">
9+
</div>
10+
<div class="form-group">
11+
<label>Attendees</label>
12+
<input class="form-control" name="ev-attendees" type="text" value="{{ newEvent.attendees }}">
13+
</div>
14+
<div class="form-row">
15+
<div class="col">
16+
<div class="form-group">
17+
<label>Start</label>
18+
<input class="form-control" name="ev-start" type="datetime-local" value="{{ newEvent.start }}">
19+
</div>
20+
</div>
21+
<div class="col">
22+
<div class="form-group">
23+
<label>End</label>
24+
<input class="form-control" name="ev-end" type="datetime-local" value="{{ newEvent.end }}">
25+
</div>
26+
</div>
27+
</div>
28+
<div class="form-group mb-3">
29+
<label>Body</label>
30+
<textarea class="form-control" name="ev-body" rows="3">{{ newEvent.body }}</textarea>
31+
</div>
32+
<input class="btn btn-primary mr-2" type="submit" value="Create" />
33+
<a class="btn btn-secondary" href="/calendar">Cancel</a>
34+
</form>
35+
<!-- </NewEventFormSnippet> -->

tutorial/02-create-app.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Before moving on, install some additional packages that you will use later:
4646
- [connect-flash](https://github.com/jaredhanson/connect-flash) to flash error messages in the app.
4747
- [express-session](https://github.com/expressjs/session) to store values in an in-memory server-side session.
4848
- [express-promise-router](https://github.com/express-promise-router/express-promise-router) to allow route handlers to return a Promise.
49+
- [express-validator](https://github.com/express-validator/express-validator) for parsing and validating form data.
4950
- [msal-node](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-node) for authenticating and getting access tokens.
5051
- [uuid](https://github.com/uuidjs/uuid) used by msal-node to generate GUIDs.
5152
- [microsoft-graph-client](https://github.com/microsoftgraph/msgraph-sdk-javascript) for making calls to Microsoft Graph.
@@ -55,7 +56,7 @@ Before moving on, install some additional packages that you will use later:
5556
5657
```Shell
5758
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
59+
npm install @azure/msal-node@1.0.0-alpha.5 @microsoft/microsoft-graph-client@2.0.0 windows-iana@4.2.1 express-validator@6.6.1
5960
```
6061
6162
> [!TIP]

tutorial/06-create-event.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!-- markdownlint-disable MD002 MD041 -->
2+
3+
In this section you will add the ability to create events on the user's calendar.
4+
5+
## Create a new event form
6+
7+
1. Create a new file in the **./views** directory named **newevent.hbs** and add the following code.
8+
9+
:::code language="html" source="../demo/graph-tutorial/views/newevent.hbs" id="NewEventFormSnippet":::
10+
11+
1. Add the following code to the **./routes/calendar.js** file before the `module.exports = router;` line.
12+
13+
:::code language="javascript" source="../demo/graph-tutorial/routes/calendar.js" id="GetEventFormSnippet":::
14+
15+
This implements a form for user input and renders it.
16+
17+
## Create the event
18+
19+
1. Open **./graph.js** and add the following function inside `module.exports`.
20+
21+
:::code language="javascript" source="../demo/graph-tutorial/graph.js" id="CreateEventSnippet":::
22+
23+
This code uses the form fields to create a Graph event object, then sends a POST request to the `/me/events` endpoint to create the event on the user's default calendar.
24+
25+
1. Add the following code to the **./routes/calendar.js** file before the `module.exports = router;` line.
26+
27+
:::code language="javascript" source="../demo/graph-tutorial/routes/calendar.js" id="PostEventFormSnippet":::
28+
29+
This code validates and sanitized the form input, then calls `graph.createEvent` to create the event. It redirects back to the calendar view after the call completes.
30+
31+
1. Save your changes and restart the app. Click the **Calendar** nav item, then click the **Create event** button. Fill in the values and click **Create**. The app returns to the calendar view once the new event is created.
32+
33+
![A screenshot of the new event form](images/create-event-01.png)
29.2 KB
Loading

0 commit comments

Comments
 (0)