Créez des applications Node.js Express avec Microsoft Graph
Ce didacticiel vous apprend à créer une application web Node.js Express qui utilise l’API Microsoft Graph pour récupérer les informations de calendrier d’un utilisateur.
Conseil
Si vous préférez simplement télécharger le didacticiel terminé, vous pouvez le télécharger de deux manières.
- Téléchargez le Node.js démarrage rapide pour obtenir du code de travail en quelques minutes.
- Téléchargez ou clonez le GitHub de données.
Conditions préalables
Avant de commencer cette démonstration, vous devez avoir installéNode.js sur votre ordinateur de développement. Si vous n’avez pas Node.js, consultez le lien précédent pour obtenir les options de téléchargement.
Notes
Windows utilisateurs devront peut-être installer Python et Visual Studio Build Tools pour prendre en charge les modules NPM qui doivent être compilés à partir de C/C++. Le programme Node.js installer sur Windows offre la possibilité d’installer automatiquement ces outils. Vous pouvez également suivre les instructions sur https://github.com/nodejs/node-gyp#on-windows.
Vous devez également avoir un compte Microsoft personnel avec une boîte aux lettres sur Outlook.com, ou un compte scolaire ou scolaire Microsoft. Si vous n’avez pas de compte Microsoft, deux options s’offrent à vous pour obtenir un compte gratuit :
- Vous pouvez vous inscrire à un nouveau compte Microsoft personnel.
- Vous pouvez vous inscrire au programme Microsoft 365 développeur pour obtenir un abonnement Microsoft 365 gratuit.
Notes
Ce didacticiel a été écrit avec Node version 14.15.0. Les étapes de ce guide peuvent fonctionner avec d’autres versions, mais elles n’ont pas été testées.
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Créer une application web Node.js Express
Dans cet exercice, vous allez utiliser Express pour créer une application web.
Ouvrez votre CLI, accédez à un répertoire dans lequel vous avez le droit de créer des fichiers et exécutez la commande suivante pour créer une application Express qui utilise handlebars comme moteur de rendu.
npx express-generator --hbs graph-tutorial
Le générateur Express crée un répertoire
graph-tutorial
appelé et génère la création de modèles pour une application Express.Accédez au
graph-tutorial
répertoire et entrez la commande suivante pour installer les dépendances.npm install
Exécutez la commande suivante pour mettre à jour les packages node avec les vulnérabilités signalées.
npm audit fix
Exécutez la commande suivante pour mettre à jour la version d’Express et d’autres dépendances.
npm install express@4.17.1 http-errors@1.8.0 morgan@1.10.0 debug@4.3.1 hbs@4.1.2
Utilisez la commande suivante pour démarrer un serveur web local.
npm start
Ouvrez votre navigateur et accédez à
http://localhost:3000
. Si tout fonctionne, vous verrez un message « Bienvenue dans Express ». Si vous ne voyez pas ce message, consultez le guide de mise en place d’Express.
Installer des packages node
Avant de passer à la suite, installez des packages supplémentaires que vous utiliserez ultérieurement :
- pour charger des valeurs à partir d’un fichier .env.
- date-fns pour la mise en forme des valeurs de date/heure.
- windows-iana pour la traduction Windows noms de fuseau horaire en ID de fuseau horaire IANA.
- connect-flash pour flasher les messages d’erreur dans l’application.
- pour stocker des valeurs dans une session côté serveur en mémoire.
- express-promise-routeur pour permettre aux routeurs de renvoyer une promesse.
- validateur express pour l’analyse et la validation des données de formulaire.
- msal-node pour l’authentification et l’obtention de jetons d’accès.
- microsoft-graph-client pour effectuer des appels à Microsoft Graph.
- isomorphic-fetch to polyfill the fetch for Node. Un polyfill de récupération est requis pour la
microsoft-graph-client
bibliothèque. Pour plus d’informations, consultez Graph wiki de bibliothèque cliente JavaScript Microsoft. - qs pour créer des chaînes de requête d’URL.
Exécutez la commande suivante dans votre CLI.
npm install dotenv@10.0.0 date-fns@2.23.0 date-fns-tz@1.1.6 connect-flash@0.1.1 express-validator@6.12.1 npm install express-session@1.17.2 express-promise-router@4.1.0 isomorphic-fetch@3.0.0 npm install @azure/msal-node@1.3.0 @microsoft/microsoft-graph-client@3.0.0 windows-iana@5.0.2
Conseil
Windows utilisateurs peuvent obtenir le message d’erreur suivant lors de la tentative d’installation de ces packages sur Windows.
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
Pour résoudre l’erreur, exécutez la commande suivante pour installer les outils de build Windows à l’aide d’une fenêtre terminal avec élévation de niveaux (administrateur) qui installe les outils de build VS et Python.
npm install --global --production windows-build-tools
Mettez à jour l’application pour utiliser l’intermédiaire
connect-flash
express-session
. Ouvrez ./app.js et ajoutez l’instructionrequire
suivante en haut du fichier.const session = require('express-session'); const flash = require('connect-flash'); const msal = require('@azure/msal-node');
Ajoutez le code suivant immédiatement après la
var app = express();
ligne.// Session middleware // NOTE: Uses default in-memory session store, which is not // suitable for production app.use(session({ secret: 'your_secret_value_here', resave: false, saveUninitialized: false, unset: 'destroy' })); // Flash middleware app.use(flash()); // Set up local vars for template layout app.use(function(req, res, next) { // Read any flashed errors and save // in the response locals res.locals.error = req.flash('error_msg'); // Check for simple error string and // convert to layout's expected format var errs = req.flash('error'); for (var i in errs){ res.locals.error.push({message: 'An error occurred', debug: errs[i]}); } // Check for an authenticated user and load // into response locals if (req.session.userId) { res.locals.user = app.locals.users[req.session.userId]; } next(); });
Concevoir l’application
Dans cette section, vous allez implémenter l’interface utilisateur de l’application.
Ouvrez ./views/layout.hbs et remplacez tout le contenu par le code suivant.
<!DOCTYPE html> <html> <head> <title>Node.js Graph Tutorial</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.1/css/all.css" crossorigin="anonymous"> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <a href="/" class="navbar-brand">Node.js Graph Tutorial</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a href="/" class="nav-link{{#if active.home}} active{{/if}}">Home</a> </li> {{#if user}} <li class="nav-item" data-turbolinks="false"> <a href="/calendar" class="nav-link{{#if active.calendar}} active{{/if}}">Calendar</a> </li> {{/if}} </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> <a class="nav-link" href="https://developer.microsoft.com/graph/docs/concepts/overview" target="_blank"> <i class="fas fa-external-link-alt mr-1"></i>Docs </a> </li> {{#if user}} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"> {{#if user.avatar}} <img src="{{ user.avatar }}" class="rounded-circle align-self-center mr-2" style="width: 32px;"> {{else}} <i class="far fa-user-circle fa-lg rounded-circle align-self-center mr-2" style="width: 32px;"></i> {{/if}} </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0">{{ user.displayName }}</h5> <p class="dropdown-item-text text-muted mb-0">{{ user.email }}</p> <div class="dropdown-divider"></div> <a href="/auth/signout" class="dropdown-item">Sign Out</a> </div> </li> {{else}} <li class="nav-item"> <a href="/auth/signin" class="nav-link">Sign In</a> </li> {{/if}} </ul> </div> </div> </nav> <main role="main" class="container"> {{#each error}} <div class="alert alert-danger" role="alert"> <p class="mb-3">{{ this.message }}</p> {{#if this.debug }} <pre class="alert-pre border bg-light p-2"><code>{{ this.debug }}</code></pre> {{/if}} </div> {{/each}} {{{body}}} </main> <!-- Bootstrap/jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script> </body> </html>
Ce code ajoute Bootstrap pour un style simple. Il définit également une disposition globale avec une barre de navigation.
Ouvrez ./public/stylesheets/style.css et remplacez tout son contenu par ce qui suit.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; }
Ouvrez ./views/index.hbs et remplacez son contenu par ce qui suit.
<div class="jumbotron"> <h1>Node.js Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from Node.js</p> {{#if user}} <h4>Welcome {{ user.displayName }}!</h4> <p>Use the navigation bar at the top of the page to get started.</p> {{else}} <a href="/auth/signin" class="btn btn-primary btn-large">Click here to sign in</a> {{/if}} </div>
Ouvrez ./routes/index.js et remplacez le code existant par ce qui suit.
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { let params = { active: { home: true } }; res.render('index', params); }); module.exports = router;
Ajoutez un fichier image de votre choix nommé no-profile-photo.png dans le répertoire ./public/images . Cette image est utilisée comme photo de l’utilisateur lorsque l’utilisateur n’a pas de photo dans Microsoft Graph.
Enregistrez toutes vos modifications et redémarrez le serveur. L’application doit maintenant avoir une apparence très différente.
Inscrire l’application sur le portail
Dans cet exercice, vous allez créer une inscription d’application web Azure AD à l’aide Azure Active Directory centre d’administration.
Ouvrez un navigateur et accédez au Centre d’administration Azure Active Directory. Connectez-vous à l’aide d’un compte personnel (compte Microsoft) ou d’un compte professionnel ou scolaire.
Sélectionnez Azure Active Directory dans le volet de navigation gauche, puis sélectionnez Inscriptions d’applications sous Gérer.
Sélectionnez Nouvelle inscription. Sur la page Inscrire une application, définissez les valeurs comme suit.
- Définissez le Nom sur
Node.js Graph Tutorial
. - Définissez les Types de comptes pris en charge sur Comptes dans un annuaire organisationnel et comptes personnels Microsoft.
- Sous URI de redirection, définissez la première flèche déroulante sur
Web
, et la valeur surhttp://localhost:3000/auth/callback
.
- Définissez le Nom sur
Sélectionner Inscription. Dans la pageNode.js Graph didacticiel, copiez la valeur de l’ID d’application (client) et enregistrez-la. Vous en aurez besoin à l’étape suivante.
Sélectionnez Certificats et secrets sous Gérer. Sélectionnez le bouton Nouveau secret client. Entrez une valeur dans Description, sélectionnez une des options pour Expire le, puis sélectionnez Ajouter.
Copiez la valeur due la clé secrète client avant de quitter cette page. Vous en aurez besoin à l’étape suivante.
Important
Ce secret client n’apparaîtra plus jamais, aussi veillez à le copier maintenant.
Ajouter une authentification Azure AD
Dans cet exercice, vous allez étendre l’application de l’exercice précédent pour prendre en charge l’authentification avec Azure AD. Cette étape est nécessaire pour obtenir le jeton d’accès OAuth nécessaire pour appeler l’Graph Microsoft. Dans cette étape, vous allez intégrer la bibliothèque msal-node dans l’application.
Créez un fichier nommé .env à la racine de votre application et ajoutez le code suivant.
OAUTH_APP_ID=YOUR_APP_ID_HERE OAUTH_APP_SECRET=YOUR_CLIENT_SECRET_HERE OAUTH_REDIRECT_URI=http://localhost:3000/auth/callback OAUTH_SCOPES='user.read,calendars.readwrite,mailboxsettings.read' OAUTH_AUTHORITY=https://login.microsoftonline.com/common/
Remplacez-le
YOUR_CLIENT_ID_HERE
par l’ID de l’application à partir du portail d’inscription des applications etYOUR_CLIENT_SECRET_HERE
par la secret client que vous avez générée.Important
Si vous utilisez un contrôle source tel que Git, il est temps d’exclure le fichier .env du contrôle source afin d’éviter toute fuite accidentelle de votre ID d’application et mot de passe.
Ouvrez ./app.js et ajoutez la ligne suivante en haut du fichier pour charger le fichier .env.
require('dotenv').config();
Implémentation de la connexion
Recherchez la
var app = express();
ligne dans ./app.js. Insérez le code suivant après cette ligne.// In-memory storage of logged-in users // For demo purposes only, production apps should store // this in a reliable storage app.locals.users = {}; // MSAL config const msalConfig = { auth: { clientId: process.env.OAUTH_APP_ID, authority: process.env.OAUTH_AUTHORITY, clientSecret: process.env.OAUTH_APP_SECRET }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { console.log(message); }, piiLoggingEnabled: false, logLevel: msal.LogLevel.Verbose, } } }; // Create msal application object app.locals.msalClient = new msal.ConfidentialClientApplication(msalConfig);
Ce code initialise la bibliothèque msal-node avec l’ID d’application et le mot de passe de l’application.
Créez un fichier dans le répertoire ./routes nommé auth.js et ajoutez le code suivant.
const router = require('express-promise-router')(); /* GET auth callback. */ router.get('/signin', async function (req, res) { const urlParameters = { scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI }; try { const authUrl = await req.app.locals .msalClient.getAuthCodeUrl(urlParameters); res.redirect(authUrl); } catch (error) { console.log(`Error: ${error}`); req.flash('error_msg', { message: 'Error getting auth URL', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); res.redirect('/'); } } ); router.get('/callback', async function(req, res) { const tokenRequest = { code: req.query.code, scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI }; try { const response = await req.app.locals .msalClient.acquireTokenByCode(tokenRequest); // TEMPORARY! // Flash the access token for testing purposes req.flash('error_msg', { message: 'Access token', debug: response.accessToken }); } catch (error) { req.flash('error_msg', { message: 'Error completing authentication', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); } res.redirect('/'); } ); router.get('/signout', async function(req, res) { // Sign out if (req.session.userId) { // Look up the user's account in the cache const accounts = await req.app.locals.msalClient .getTokenCache() .getAllAccounts(); const userAccount = accounts.find(a => a.homeAccountId === req.session.userId); // Remove the account if (userAccount) { req.app.locals.msalClient .getTokenCache() .removeAccount(userAccount); } } // Destroy the user's session req.session.destroy(function (err) { res.redirect('/'); }); } ); module.exports = router;
Cela définit un routeur avec trois itinéraires :
signin
,callback
etsignout
.L’itinéraire
signin
appelle lagetAuthCodeUrl
fonction pour générer l’URL de connexion, puis redirige le navigateur vers cette URL.L’itinéraire
callback
est l’endroit où Azure redirige une fois la signature terminée. Le code appelle la fonctionacquireTokenByCode
pour échanger le code d’autorisation contre un jeton d’accès. Une fois le jeton obtenu, il est redirigé vers la page d’accueil avec le jeton d’accès dans la valeur d’erreur temporaire. Nous allons l’utiliser pour vérifier que notre connectez-vous fonctionne avant de passer à autre chose. Avant de tester, nous devons configurer l’application Express pour utiliser le nouveau routeur à partir de ./routes/auth.js.La
signout
méthode déconnecte l’utilisateur et détruit la session.Ouvrez ./app.js et insérez le code suivant avant la
var app = express();
ligne.const authRouter = require('./routes/auth');
Insérez le code suivant après la
app.use('/', indexRouter);
ligne.app.use('/auth', authRouter);
Démarrez le serveur et accédez à https://localhost:3000
. Cliquez sur le bouton de connexion. vous serez redirigé vers https://login.microsoftonline.com
. Connectez-vous avec votre compte Microsoft et consentez aux autorisations demandées. Le navigateur vous redirige vers l’application, affichant le jeton.
Obtenir les détails de l’utilisateur
Créez un fichier à la racine du projet nommé graph.js et ajoutez le code suivant.
var graph = require('@microsoft/microsoft-graph-client'); require('isomorphic-fetch'); module.exports = { getUserDetails: async function(msalClient, userId) { const client = getAuthenticatedClient(msalClient, userId); const user = await client .api('/me') .select('displayName,mail,mailboxSettings,userPrincipalName') .get(); return user; }, }; function getAuthenticatedClient(msalClient, userId) { if (!msalClient || !userId) { throw new Error( `Invalid MSAL state. Client: ${msalClient ? 'present' : 'missing'}, User ID: ${userId ? 'present' : 'missing'}`); } // Initialize Graph client const client = graph.Client.init({ // Implement an auth provider that gets a token // from the app's MSAL instance authProvider: async (done) => { try { // Get the user's account const account = await msalClient .getTokenCache() .getAccountByHomeId(userId); if (account) { // Attempt to get the token silently // This method uses the token cache and // refreshes expired tokens as needed const response = await msalClient.acquireTokenSilent({ scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI, account: account }); // First param to callback is the error, // Set to null in success case done(null, response.accessToken); } } catch (err) { console.log(JSON.stringify(err, Object.getOwnPropertyNames(err))); done(err, null); } } }); return client; }
Cela exporte la
getUserDetails
fonction, qui utilise le SDK/me
Microsoft Graph pour appeler le point de terminaison et renvoyer le résultat.Ouvrez ./routes/auth.js et ajoutez
require
les instructions suivantes en haut du fichier.const graph = require('../graph');
Remplacez l’itinéraire de rappel existant par le code suivant.
router.get('/callback', async function(req, res) { const tokenRequest = { code: req.query.code, scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI }; try { const response = await req.app.locals .msalClient.acquireTokenByCode(tokenRequest); // Save the user's homeAccountId in their session req.session.userId = response.account.homeAccountId; const user = await graph.getUserDetails(response.accessToken); // Add the user to user storage req.app.locals.users[req.session.userId] = { displayName: user.displayName, email: user.mail || user.userPrincipalName, timeZone: user.mailboxSettings.timeZone }; } catch(error) { req.flash('error_msg', { message: 'Error completing authentication', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); } res.redirect('/'); } );
Le nouveau code enregistre l’ID de compte de l’utilisateur dans la session, obtient les détails de l’utilisateur auprès de Microsoft Graph et l’enregistre dans le stockage utilisateur de l’application.
Redémarrez le serveur et traversez le processus de sign-in. Vous devez revenir sur la page d’accueil, mais l’interface utilisateur doit changer pour indiquer que vous êtes en cours de signature.
Cliquez sur l’avatar de l’utilisateur dans le coin supérieur droit pour accéder au lien de connexion. Le fait de cliquer sur Se déconnecter réinitialise la session et vous ramène à la page d’accueil.
Stockage et actualisation des jetons
À ce stade, votre application dispose d’un jeton d’accès, qui est Authorization
envoyé dans l’en-tête des appels d’API. Il s’agit du jeton qui permet à l’application d’accéder au Graph Microsoft au nom de l’utilisateur.
Cependant, ce jeton est de courte durée. Le jeton expire une heure après son émission. C’est là que le jeton d’actualisation devient utile. La spécification OAuth introduit un jeton d’actualisation, qui permet à l’application de demander un nouveau jeton d’accès sans que l’utilisateur ne se connecte à nouveau.
Étant donné que l’application utilise le package msal-node, vous n’avez pas besoin d’implémenter de logique de stockage ou d’actualisation de jeton. L’application utilise le cache de jetons en mémoire msal-node par défaut, ce qui est suffisant pour un exemple d’application. Les applications de production doivent fournir leur propre plug-in de mise en cache pour sérialiser le cache de jetons dans un moyen de stockage sécurisé et fiable.
Obtenir un affichage Calendrier
Dans cet exercice, vous allez incorporer Microsoft Graph dans l’application. Pour cette application, vous allez utiliser la bibliothèque cliente microsoft-graph pour appeler Microsoft Graph.
Récupérer les événements de calendrier à partir d’Outlook
Ouvrez ./graph.js et ajoutez la fonction suivante à l’intérieur
module.exports
.getCalendarView: async function(accessToken, start, end, timeZone) { const client = getAuthenticatedClient(accessToken); const events = await client .api('/me/calendarview') // Add Prefer header to get back times in user's timezone .header("Prefer", `outlook.timezone="${timeZone}"`) // Add the begin and end of the calendar window .query({ startDateTime: start, endDateTime: end }) // Get just the properties used by the app .select('subject,organizer,start,end') // Order by start time .orderby('start/dateTime') // Get at most 50 results .top(50) .get(); return events; },
Que fait ce code ?
- L’URL qui sera appelée est
/me/calendarview
. - La
header
méthode ajoute l’en-têtePrefer: outlook.timezone
à la demande, ce qui entraîne le retour des heures de début et de fin dans le fuseau horaire de l’utilisateur. - La
query
méthode définit les paramètresstartDateTime
etendDateTime
les paramètres de l’affichage Calendrier. - La
select
méthode limite les champs renvoyés pour chaque événement à ceux que l’affichage utilisera réellement. - La
orderby
méthode trie les résultats par heure de début. - La
top
méthode limite les résultats à 50 événements.
- L’URL qui sera appelée est
Créez un fichier dans le répertoire ./routes nommé calendar.js et ajoutez le code suivant.
const router = require('express-promise-router')(); const graph = require('../graph.js'); const addDays = require('date-fns/addDays'); const formatISO = require('date-fns/formatISO'); const startOfWeek = require('date-fns/startOfWeek'); const zonedTimeToUtc = require('date-fns-tz/zonedTimeToUtc'); const iana = require('windows-iana'); const { body, validationResult } = require('express-validator'); const validator = require('validator'); /* GET /calendar */ router.get('/', async function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { const params = { active: { calendar: true } }; // Get the user const user = req.app.locals.users[req.session.userId]; // Convert user's Windows time zone ("Pacific Standard Time") // to IANA format ("America/Los_Angeles") const timeZoneId = iana.findIana(user.timeZone)[0]; console.log(`Time zone: ${timeZoneId.valueOf()}`); // Calculate the start and end of the current week // Get midnight on the start of the current week in the user's timezone, // but in UTC. For example, for Pacific Standard Time, the time value would be // 07:00:00Z var weekStart = zonedTimeToUtc(startOfWeek(new Date()), timeZoneId.valueOf()); var weekEnd = addDays(weekStart, 7); console.log(`Start: ${formatISO(weekStart)}`); try { // Get the events const events = await graph.getCalendarView( req.app.locals.msalClient, req.session.userId, formatISO(weekStart), formatISO(weekEnd), user.timeZone); res.json(events.value); } catch (err) { res.send(JSON.stringify(err, Object.getOwnPropertyNames(err))); } } } ); module.exports = router;
Mettez à jour ./app.js pour utiliser ce nouvel itinéraire. Ajoutez la ligne suivante avant la
var app = express();
ligne.const calendarRouter = require('./routes/calendar');
Ajoutez la ligne suivante après la
app.use('/auth', authRouter);
ligne.app.use('/calendar', calendarRouter);
Redémarrez le serveur. Connectez-vous et cliquez sur le lien Calendrier dans la barre de navigation. Si tout fonctionne, vous devriez voir une image mémoire JSON des événements dans le calendrier de l’utilisateur.
Afficher les résultats
Vous pouvez désormais ajouter une vue pour afficher les résultats de façon plus parlante.
Ajoutez le code suivant dans ./app.js après la
app.set('view engine', 'hbs');
ligne.var hbs = require('hbs'); var parseISO = require('date-fns/parseISO'); var formatDate = require('date-fns/format'); // Helper to format date/time sent by Graph hbs.registerHelper('eventDateTime', function(dateTime) { const date = parseISO(dateTime); return formatDate(date, 'M/d/yy h:mm a'); });
Cela implémente un outil d’aide Handlebars pour mettre en forme la date ISO 8601 renvoyée par Microsoft Graph en quelque chose de plus convivial.
Créez un fichier dans le répertoire ./views nommé calendar.hbs et ajoutez le code suivant.
<h1 class="mb-3">Calendar</h1> <a href="/calendar/new" class="btn btn-light btn-sm mb-3">New event</a> <table class="table"> <thead> <tr> <th scope="col">Organizer</th> <th scope="col">Subject</th> <th scope="col">Start</th> <th scope="col">End</th> </tr> </thead> <tbody> {{#each events}} <tr> <td>{{this.organizer.emailAddress.name}}</td> <td>{{this.subject}}</td> <td>{{eventDateTime this.start.dateTime}}</td> <td>{{eventDateTime this.end.dateTime}}</td> </tr> {{/each}} </tbody> </table>
Cela permet de parcourir une collection d’événements et d’ajouter une ligne de tableau pour chacun d’eux.
Maintenant, mettez à jour l’itinéraire dans ./routes/calendar.js pour utiliser cet affichage. Remplacez l’itinéraire existant par le code suivant.
/* GET /calendar */ router.get('/', async function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { const params = { active: { calendar: true } }; // Get the user const user = req.app.locals.users[req.session.userId]; // Convert user's Windows time zone ("Pacific Standard Time") // to IANA format ("America/Los_Angeles") const timeZoneId = iana.findIana(user.timeZone)[0]; console.log(`Time zone: ${timeZoneId.valueOf()}`); // Calculate the start and end of the current week // Get midnight on the start of the current week in the user's timezone, // but in UTC. For example, for Pacific Standard Time, the time value would be // 07:00:00Z var weekStart = zonedTimeToUtc(startOfWeek(new Date()), timeZoneId.valueOf()); var weekEnd = addDays(weekStart, 7); console.log(`Start: ${formatISO(weekStart)}`); // Get the access token var accessToken; try { accessToken = await getAccessToken(req.session.userId, req.app.locals.msalClient); } catch (err) { req.flash('error_msg', { message: 'Could not get access token. Try signing out and signing in again.', debug: JSON.stringify(err, Object.getOwnPropertyNames(err)) }); return; } if (accessToken && accessToken.length > 0) { try { // Get the events const events = await graph.getCalendarView( accessToken, formatISO(weekStart), formatISO(weekEnd), user.timeZone); params.events = events.value; } catch (err) { req.flash('error_msg', { message: 'Could not fetch events', debug: JSON.stringify(err, Object.getOwnPropertyNames(err)) }); } } else { req.flash('error_msg', 'Could not get an access token'); } res.render('calendar', params); } } );
Enregistrez vos modifications, redémarrez le serveur et connectez-vous à l’application. Cliquez sur le lien Calendrier et l’application doit maintenant restituer une table des événements.
Créer un événement
Dans cette section, vous allez ajouter la possibilité de créer des événements sur le calendrier de l’utilisateur.
Créer un formulaire d’événement
Créez un fichier dans le répertoire ./views nommé newevent.hbs et ajoutez le code suivant.
<form method="POST"> <div class="form-group"> <label>Subject</label> <input class="form-control" name="ev-subject" type="text" value="{{ newEvent.subject }}"> </div> <div class="form-group"> <label>Attendees</label> <input class="form-control" name="ev-attendees" type="text" value="{{ newEvent.attendees }}"> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <label>Start</label> <input class="form-control" name="ev-start" type="datetime-local" value="{{ newEvent.start }}"> </div> </div> <div class="col"> <div class="form-group"> <label>End</label> <input class="form-control" name="ev-end" type="datetime-local" value="{{ newEvent.end }}"> </div> </div> </div> <div class="form-group mb-3"> <label>Body</label> <textarea class="form-control" name="ev-body" rows="3">{{ newEvent.body }}</textarea> </div> <input class="btn btn-primary mr-2" type="submit" value="Create" /> <a class="btn btn-secondary" href="/calendar">Cancel</a> </form>
Ajoutez le code suivant au fichier ./routes/calendar.js avant la
module.exports = router;
ligne./* GET /calendar/new */ router.get('/new', function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { res.locals.newEvent = {}; res.render('newevent'); } } );
Cela implémente un formulaire pour l’entrée utilisateur et le restituer.
Créer l’événement
Ouvrez ./graph.js et ajoutez la fonction suivante à l’intérieur.
module.exports
createEvent: async function(accessToken, formData, timeZone) { const client = getAuthenticatedClient(accessToken); // Build a Graph event const newEvent = { subject: formData.subject, start: { dateTime: formData.start, timeZone: timeZone }, end: { dateTime: formData.end, timeZone: timeZone }, body: { contentType: 'text', content: formData.body } }; // Add attendees if present if (formData.attendees) { newEvent.attendees = []; formData.attendees.forEach(attendee => { newEvent.attendees.push({ type: 'required', emailAddress: { address: attendee } }); }); } // POST /me/events await client .api('/me/events') .post(newEvent); },
Ce code utilise les champs de formulaire pour créer un objet d’événement Graph, puis envoie une requête POST au point de terminaison pour créer l’événement sur le calendrier par défaut de
/me/events
l’utilisateur.Ajoutez le code suivant au fichier ./routes/calendar.js avant la
module.exports = router;
ligne./* POST /calendar/new */ router.post('/new', [ body('ev-subject').escape(), // Custom sanitizer converts ;-delimited string // to an array of strings body('ev-attendees').customSanitizer(value => { return value.split(';'); // Custom validator to make sure each // entry is an email address }).custom(value => { value.forEach(element => { if (!validator.isEmail(element)) { throw new Error('Invalid email address'); } }); return true; }), // Ensure start and end are ISO 8601 date-time values body('ev-start').isISO8601(), body('ev-end').isISO8601(), body('ev-body').escape() ], async function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { // Build an object from the form values const formData = { subject: req.body['ev-subject'], attendees: req.body['ev-attendees'], start: req.body['ev-start'], end: req.body['ev-end'], body: req.body['ev-body'] }; // Check if there are any errors with the form values const formErrors = validationResult(req); if (!formErrors.isEmpty()) { let invalidFields = ''; formErrors.errors.forEach(error => { invalidFields += `${error.param.slice(3, error.param.length)},` }); // Preserve the user's input when re-rendering the form // Convert the attendees array back to a string formData.attendees = formData.attendees.join(';'); return res.render('newevent', { newEvent: formData, error: [{ message: `Invalid input in the following fields: ${invalidFields}` }] }); } // Get the access token var accessToken; try { accessToken = await getAccessToken(req.session.userId, req.app.locals.msalClient); } catch (err) { req.flash('error_msg', { message: 'Could not get access token. Try signing out and signing in again.', debug: JSON.stringify(err, Object.getOwnPropertyNames(err)) }); return; } // Get the user const user = req.app.locals.users[req.session.userId]; // Create the event try { await graph.createEvent(accessToken, formData, user.timeZone); } catch (error) { req.flash('error_msg', { message: 'Could not create event', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); } // Redirect back to the calendar view return res.redirect('/calendar'); } } );
Ce code valide et désinfecte l’entrée du formulaire, puis appelle
graph.createEvent
pour créer l’événement. Il redirige vers l’affichage Calendrier une fois l’appel terminé.Enregistrez vos modifications, puis redémarrez l’application. Cliquez sur l’élément de navigation Calendrier, puis cliquez sur le bouton Créer un événement. Remplissez les valeurs et cliquez sur Créer. L’application revient à l’affichage Calendrier une fois le nouvel événement créé.
Félicitations !
Vous avez terminé le didacticiel Node.js Microsoft Graph. Maintenant que vous disposez d’une application de travail qui appelle Microsoft Graph, vous pouvez expérimenter et ajouter de nouvelles fonctionnalités. Consultez la vue d’ensemble de Microsoft Graph pour voir toutes les données accessibles avec Microsoft Graph.
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Vous avez un problème avec cette section ? Si c'est le cas, faites-nous part de vos commentaires pour que nous puissions l'améliorer.