Crear aplicaciones de Node.js Express con Microsoft Graph
Este tutorial le enseña a crear una aplicación web de Node.js Express que use la API de Microsoft Graph para recuperar información de calendario para un usuario.
Sugerencia
Si prefiere descargar el tutorial completado, puede descargarlo de dos maneras.
- Descargue el Node.js rápido para obtener código de trabajo en minutos.
- Descargue o clone el GitHub repositorio.
Requisitos previos
Antes de iniciar esta demostración, debe haber Node.js instalado en el equipo de desarrollo. Si no tiene Node.js, visite el vínculo anterior para ver las opciones de descarga.
Nota
Windows usuarios deben instalar Python y Visual Studio Build Tools para admitir módulos NPM que deben compilarse desde C/C++. El Node.js en Windows ofrece una opción para instalar automáticamente estas herramientas. Como alternativa, puede seguir las instrucciones en https://github.com/nodejs/node-gyp#on-windows.
También debe tener una cuenta personal de Microsoft con un buzón en Outlook.com, o una cuenta de Trabajo o escuela de Microsoft. Si no tienes una cuenta de Microsoft, hay un par de opciones para obtener una cuenta gratuita:
- Puedes suscribirte a una nueva cuenta personal de Microsoft.
- Puedes suscribirte al programa Microsoft 365 desarrolladores para obtener una suscripción Microsoft 365 gratuita.
Nota
Este tutorial se escribió con Node versión 14.15.0. Los pasos de esta guía pueden funcionar con otras versiones, pero eso no se ha probado.
Comentarios
Proporcione cualquier comentario sobre este tutorial en el repositorio GitHub usuario.
Crear una aplicación web de Node.js Express
En este ejercicio, usarás Express para crear una aplicación web.
Abra la CLI, vaya a un directorio donde tenga derechos para crear archivos y ejecute el siguiente comando para crear una nueva aplicación Express que use Handlebars como motor de representación.
npx express-generator --hbs graph-tutorial
El generador de Express crea un nuevo directorio llamado
graph-tutorial
y scaffolding de una aplicación Express.Vaya al directorio
graph-tutorial
y escriba el siguiente comando para instalar dependencias.npm install
Ejecute el siguiente comando para actualizar los paquetes de Node con vulnerabilidades notificadas.
npm audit fix
Ejecute el siguiente comando para actualizar la versión de Express y otras dependencias.
npm install express@4.17.1 http-errors@1.8.0 morgan@1.10.0 debug@4.3.1 hbs@4.1.2
Use el siguiente comando para iniciar un servidor web local.
npm start
Abra el explorador y vaya a
http://localhost:3000
. Si todo funciona, verá un mensaje "Bienvenido a Express". Si no ves ese mensaje, consulta la guía de introducción de Express.
Instalar paquetes de nodo
Antes de seguir adelante, instale algunos paquetes adicionales que usará más adelante:
- dotenv para cargar valores desde un archivo .env.
- date-fns para dar formato a los valores de fecha y hora.
- windows-iana para traducir Windows nombres de zona horaria a los IDs de zona horaria de IANA.
- connect-flash to flash error messages in the app.
- express-session para almacenar valores en una sesión del lado servidor en memoria.
- express-promise-router para permitir que los controladores de ruta devuelvan una promesa.
- express-validator para analizar y validar datos de formulario.
- msal-node para autenticar y obtener tokens de acceso.
- microsoft-graph-client para realizar llamadas a Microsoft Graph.
- isomorphic-fetch to polyfill the fetch for Node. Se requiere un polyfill de captura para la
microsoft-graph-client
biblioteca. Consulta el wiki de biblioteca Graph cliente de JavaScript de Microsoft para obtener más información. - qs para crear cadenas de consulta de dirección URL.
Ejecute el siguiente comando en la 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
Sugerencia
Windows usuarios pueden obtener el siguiente mensaje de error al intentar instalar estos paquetes en Windows.
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
Para resolver el error, ejecute el siguiente comando para instalar las herramientas de compilación de Windows mediante una ventana de terminal con privilegios elevados (administrador), que instala vs Build Tools y Python.
npm install --global --production windows-build-tools
Actualice la aplicación para usar el software intermedio
connect-flash
yexpress-session
. Abra ./app.js y agregue la siguienterequire
instrucción a la parte superior del archivo.const session = require('express-session'); const flash = require('connect-flash'); const msal = require('@azure/msal-node');
Agregue el siguiente código inmediatamente después de la
var app = express();
línea.// 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(); });
Diseñar la aplicación
En esta sección, implementará la interfaz de usuario de la aplicación.
Abra ./views/layout.hbs y reemplace todo el contenido por el código siguiente.
<!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>
Este código agrega Bootstrap para un estilo sencillo. También define un diseño global con una barra de navegación.
Abra ./public/stylesheets/style.css y reemplace todo su contenido por lo siguiente.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; }
Abra ./views/index.hbs y reemplace su contenido por lo siguiente.
<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>
Abra ./routes/index.js y reemplace el código existente por el siguiente.
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;
Agregue un archivo de imagen de su elección denominado no-profile-photo.png en el directorio ./public/images . Esta imagen se usará como foto del usuario cuando el usuario no tenga ninguna foto en Microsoft Graph.
Guarde todos los cambios y reinicie el servidor. Ahora, la aplicación debe tener un aspecto muy diferente.
Registrar la aplicación en el portal
En este ejercicio, creará un nuevo registro de aplicaciones web de Azure AD mediante el Centro Azure Active Directory administración.
Abra un explorador y vaya al centro de administración de Azure Active Directory. Inicie sesión con una cuenta personal (también conocida como: cuenta Microsoft) o una cuenta profesional o educativa.
Seleccione Azure Active Directory en el panel de navegación izquierdo y, a continuación, seleccione Registros de aplicaciones en Administrar.
Seleccione Nuevo registro. En la página Registrar una aplicación, establezca los valores siguientes.
- Establezca Nombre como
Node.js Graph Tutorial
. - Establezca Tipos de cuenta admitidos en Cuentas en cualquier directorio de organización y cuentas personales de Microsoft.
- En URI de redirección, establezca la primera lista desplegable en
Web
y establezca el valorhttp://localhost:3000/auth/callback
.
- Establezca Nombre como
Seleccione Registrar. En la Node.js Graph tutorial, copie el valor del identificador de aplicación (cliente) y guárdelo, lo necesitará en el siguiente paso.
Seleccione Certificados y secretos en Administrar. Seleccione el botón Nuevo secreto de cliente. Escriba un valor en Descripción, y seleccione una de las opciones para Expira, y después, Agregar.
Copie el valor del secreto del cliente antes de salir de esta página. Lo necesitará en el siguiente paso.
Importante
El secreto de cliente no se vuelve a mostrar, así que asegúrese de copiarlo en este momento.
Agregar autenticación de Azure AD
En este ejercicio, extenderá la aplicación desde el ejercicio anterior para admitir la autenticación con Azure AD. Esto es necesario para obtener el token de acceso OAuth necesario para llamar a Microsoft Graph. En este paso, integrará la biblioteca msal-node en la aplicación.
Cree un nuevo archivo denominado .env en la raíz de la aplicación y agregue el siguiente código.
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/
Reemplace
YOUR_CLIENT_ID_HERE
por el identificador de aplicación del Portal de registro de aplicaciones y reemplaceYOUR_CLIENT_SECRET_HERE
por el secreto de cliente que generó.Importante
Si usas el control de código fuente como git, ahora sería un buen momento para excluir el archivo .env del control de código fuente para evitar la pérdida involuntaria del identificador y la contraseña de la aplicación.
Abra ./app.js y agregue la siguiente línea a la parte superior del archivo para cargar el archivo .env .
require('dotenv').config();
Implementar el inicio de sesión
Busque la línea
var app = express();
en ./app.js. Inserte el siguiente código después de esa línea.// 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);
Este código inicializa la biblioteca msal-node con el identificador de aplicación y la contraseña de la aplicación.
Cree un nuevo archivo en el directorio ./routes denominadoauth.js y agregue el siguiente código.
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;
Esto define un enrutador con tres rutas:
signin
,callback
ysignout
.La
signin
ruta llama a lagetAuthCodeUrl
función para generar la dirección URL de inicio de sesión y, a continuación, redirige el explorador a esa dirección URL.La
callback
ruta es donde Azure redirige una vez completado el inicio de sesión. El código llama a laacquireTokenByCode
función para intercambiar el código de autorización de un token de acceso. Una vez obtenido el token, vuelve a la página principal con el token de acceso en el valor de error temporal. Lo usaremos para comprobar que nuestro inicio de sesión funciona antes de seguir adelante. Antes de probar, debemos configurar la aplicación Express para que use el nuevo enrutador de ./routes/auth.js.El
signout
método cierra la sesión del usuario y destruye la sesión.Abra ./app.js e inserte el siguiente código antes de la
var app = express();
línea.const authRouter = require('./routes/auth');
Inserte el siguiente código después de la
app.use('/', indexRouter);
línea.app.use('/auth', authRouter);
Inicie el servidor y vaya a https://localhost:3000
. Haga clic en el botón de inicio de sesión y se le redirigirá a https://login.microsoftonline.com
. Inicie sesión con su cuenta de Microsoft y consiente los permisos solicitados. El explorador redirige a la aplicación, que muestra el token.
Obtener detalles del usuario
Cree un nuevo archivo en la raíz del proyecto denominado graph.js y agregue el siguiente código.
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; }
Esto exporta la
getUserDetails
función, que usa el SDK de Microsoft Graph para llamar/me
al extremo y devolver el resultado.Abra ./routes/auth.js y agregue las siguientes
require
instrucciones a la parte superior del archivo.const graph = require('../graph');
Reemplace la ruta de devolución de llamada existente por el código siguiente.
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('/'); } );
El nuevo código guarda el identificador de cuenta del usuario en la sesión, obtiene los detalles del usuario de Microsoft Graph y lo guarda en el almacenamiento de usuario de la aplicación.
Reinicie el servidor y pase por el proceso de inicio de sesión. Debes volver a la página principal, pero la interfaz de usuario debe cambiar para indicar que has iniciado sesión.
Haga clic en el avatar del usuario en la esquina superior derecha para obtener acceso al vínculo Cerrar sesión . Al hacer clic en Cerrar sesión, se restablece la sesión y se devuelve a la página principal.
Almacenar y actualizar tokens
En este momento, la aplicación tiene un token de acceso, que se envía en el encabezado Authorization
de las llamadas API. Este es el token que permite a la aplicación tener acceso al Graph microsoft en nombre del usuario.
Sin embargo, este token es de corta duración. El token expira una hora después de su emisión. Aquí es donde el token de actualización resulta útil. La especificación OAuth presenta un token de actualización, que permite a la aplicación solicitar un nuevo token de acceso sin necesidad de que el usuario vuelva a iniciar sesión.
Dado que la aplicación usa el paquete msal-node, no es necesario implementar ninguna lógica de actualización o almacenamiento de tokens. La aplicación usa la memoria caché de tokens en memoria msal-node predeterminada, lo que es suficiente para una aplicación de ejemplo. Las aplicaciones de producción deben proporcionar su propio complemento de almacenamiento en caché para serializar la caché de tokens en un medio de almacenamiento seguro y confiable.
Obtener una vista de calendario
En este ejercicio, incorporará Microsoft Graph en la aplicación. Para esta aplicación, usará la biblioteca de microsoft-graph-client para realizar llamadas a Microsoft Graph.
Obtener eventos del calendario desde Outlook
Abra ./graph.js y agregue la siguiente función dentro de
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; },
Tenga en cuenta lo que está haciendo este código.
- La dirección URL a la que se llamará es
/me/calendarview
. - El
header
método agrega el encabezadoPrefer: outlook.timezone
a la solicitud, lo que hace que las horas de inicio y finalización se devuelvan en la zona horaria del usuario. - El
query
método establece los parámetrosstartDateTime
yendDateTime
de la vista de calendario. - El
select
método limita los campos devueltos para cada evento a solo aquellos que la vista usará realmente. - El
orderby
método ordena los resultados por hora de inicio. - El
top
método limita los resultados a 50 eventos.
- La dirección URL a la que se llamará es
Cree un nuevo archivo en el directorio ./routes denominadocalendar.js y agregue el siguiente código.
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;
Actualice ./app.js para usar esta nueva ruta. Agregue la siguiente línea antes de la
var app = express();
línea.const calendarRouter = require('./routes/calendar');
Agregue la siguiente línea después de la
app.use('/auth', authRouter);
línea.app.use('/calendar', calendarRouter);
Reinicie el servidor. Inicie sesión y haga clic en el vínculo Calendario de la barra de navegación. Si funciona todo, debería ver un volcado JSON de eventos en el calendario del usuario.
Mostrar los resultados
Ahora puede agregar una vista para mostrar los resultados de una manera más fácil de usar.
Agregue el siguiente código en ./app.js después de la
app.set('view engine', 'hbs');
línea.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'); });
Esto implementa una aplicación auxiliar Handlebars para dar formato a la fecha ISO 8601 devuelta por Microsoft Graph en algo más fácil de usar.
Cree un nuevo archivo en el directorio ./views denominado calendar.hbs y agregue el siguiente código.
<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>
Esto recorrerá una colección de eventos y agregará una fila de tabla para cada uno.
Ahora actualice la ruta en ./routes/calendar.js para usar esta vista. Reemplace la ruta existente por el código siguiente.
/* 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); } } );
Guarde los cambios, reinicie el servidor e inicie sesión en la aplicación. Haz clic en el vínculo Calendario y la aplicación ahora debe representar una tabla de eventos.
Crear un nuevo evento
En esta sección, agregará la capacidad de crear eventos en el calendario del usuario.
Crear un formulario de evento nuevo
Cree un nuevo archivo en el directorio ./views denominado newevent.hbs y agregue el siguiente código.
<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>
Agregue el siguiente código al archivo ./routes/calendar.js antes de la
module.exports = router;
línea./* 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'); } } );
Esto implementa un formulario para la entrada del usuario y lo representa.
Crear el evento
Abra ./graph.js y agregue la siguiente función dentro
module.exports
de .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); },
Este código usa los campos de formulario para crear un objeto de evento Graph y, a continuación, envía una solicitud POST al extremo para crear el evento en el calendario
/me/events
predeterminado del usuario.Agregue el siguiente código al archivo ./routes/calendar.js antes de la
module.exports = router;
línea./* 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'); } } );
Este código valida y desinfecta la entrada del formulario y, a continuación, llama
graph.createEvent
para crear el evento. Se redirige de nuevo a la vista de calendario una vez completada la llamada.Guarde los cambios y reinicie la aplicación. Haga clic en el elemento de navegación Calendario y, a continuación, haga clic en el botón Crear evento. Rellene los valores y haga clic en Crear. La aplicación vuelve a la vista de calendario una vez creado el nuevo evento.
¡Enhorabuena!
Ha completado el tutorial Node.js microsoft Graph microsoft. Ahora que tienes una aplicación de trabajo que llama a Microsoft Graph, puedes experimentar y agregar nuevas características. Visite la información general de Microsoft Graph para ver todos los datos a los que puede acceder con Microsoft Graph.
Comentarios
Proporcione cualquier comentario sobre este tutorial en el repositorio GitHub archivo.
¿Tiene algún problema con esta sección? Si es así, envíenos sus comentarios para que podamos mejorarla.