Criar aplicativos Node.js Express com o Microsoft Graph
Este tutorial ensina como criar um aplicativo Web Node.js Express que usa a API do Microsoft Graph para recuperar informações de calendário para um usuário.
Dica
Se você preferir apenas baixar o tutorial concluído, poderá baixá-lo de duas maneiras.
- Baixe o Node.js início rápido para obter o código de trabalho em minutos.
- Baixe ou clone o GitHub repositório.
Pré-requisitos
Antes de iniciar essa demonstração, você deve terNode.js instalado em sua máquina de desenvolvimento. Se você não tiver Node.js, visite o link anterior para opções de download.
Observação
Windows usuários podem precisar instalar o Python e Ferramentas de Build do Visual Studio para dar suporte a módulos NPM que precisam ser compilados a partir de C/C++. O Node.js instalador no Windows oferece uma opção para instalar automaticamente essas ferramentas. Como alternativa, você pode seguir instruções em https://github.com/nodejs/node-gyp#on-windows.
Você também deve ter uma conta pessoal da Microsoft com uma caixa de correio em Outlook.com, ou uma conta de trabalho ou de estudante da Microsoft. Se você não tiver uma conta da Microsoft, há algumas opções para obter uma conta gratuita:
- Você pode se inscrever em uma nova conta pessoal da Microsoft.
- Você pode se inscrever no programa Microsoft 365 desenvolvedor para obter uma assinatura Microsoft 365 gratuita.
Observação
Este tutorial foi escrito com o Nó versão 14.15.0. As etapas neste guia podem funcionar com outras versões, mas que não foram testadas.
Comentários
Forneça qualquer comentário sobre este tutorial no repositório GitHub.
Criar um aplicativo Web do Node.js Express
Neste exercício, você usará o Express para criar um aplicativo Web.
Abra sua CLI, navegue até um diretório em que você tem direitos para criar arquivos e execute o seguinte comando para criar um novo aplicativo Express que usa Guidões como o mecanismo de renderização.
npx express-generator --hbs graph-tutorial
O gerador Express cria um novo diretório chamado
graph-tutorial
e estrutura um aplicativo Express.Navegue até o
graph-tutorial
diretório e insira o seguinte comando para instalar dependências.npm install
Execute o seguinte comando para atualizar pacotes de nó com vulnerabilidades relatadas.
npm audit fix
Execute o seguinte comando para atualizar a versão do Express e outras dependências.
npm install express@4.17.1 http-errors@1.8.0 morgan@1.10.0 debug@4.3.1 hbs@4.1.2
Use o seguinte comando para iniciar um servidor Web local.
npm start
Abra o navegador e vá até
http://localhost:3000
. Se tudo estiver funcionando, você verá uma mensagem "Bem-vindo ao Express". Se você não vir essa mensagem, verifique o guia De início do Express.
Instalar pacotes de nó
Antes de continuar, instale alguns pacotes adicionais que você usará posteriormente:
- dotenv para carregar valores de um arquivo .env.
- date-fns para formatação de valores de data/hora.
- windows-iana para traduzir Windows nomes de fuso horário para IDs de fuso horário IANA.
- connect-flash para mensagens de erro flash no aplicativo.
- express-session para armazenar valores em uma sessão no lado do servidor na memória.
- express-promise-router para permitir que manipuladores de rota retornem um Promise.
- express-validator para análise e validação de dados de formulário.
- msal-node para autenticar e obter tokens de acesso.
- microsoft-graph-client para fazer chamadas para o Microsoft Graph.
- isomorphic-fetch para polifilar a busca para Node. Um polifilo de busca é necessário para a
microsoft-graph-client
biblioteca. Consulte o wiki da biblioteca de Graph javaScript do Microsoft Graph para obter mais informações. - qs para criar cadeias de caracteres de consulta de URL.
Execute o seguinte comando em sua 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
Dica
Windows os usuários podem receber a seguinte mensagem de erro ao tentar instalar esses pacotes Windows.
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
Para resolver o erro, execute o seguinte comando para instalar o Windows ferramentas de com build usando uma janela de terminal com privilégios elevados (Administrador) que instala as Ferramentas de Com build vs e Python.
npm install --global --production windows-build-tools
Atualize o aplicativo para usar o
connect-flash
middleware eexpress-session
. Abra ./app.js e adicione a instrução arequire
seguir à parte superior do arquivo.const session = require('express-session'); const flash = require('connect-flash'); const msal = require('@azure/msal-node');
Adicione o seguinte código imediatamente após a
var app = express();
linha.// 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(); });
Design do aplicativo
Nesta seção, você implementará a interface do usuário do aplicativo.
Abra ./views/layout.hbs e substitua todo o conteúdo pelo código a seguir.
<!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 adiciona Bootstrap para estilo simples. Ele também define um layout global com uma barra de nav.
Abra ./public/stylesheets/style.css e substitua todo o conteúdo pelo seguinte.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; }
Abra ./views/index.hbs e substitua seu conteúdo pelo seguinte.
<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 e substitua o código existente pelo seguinte.
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;
Adicione um arquivo de imagem de sua escolhano-profile-photo.pngno diretório ./public/images . Essa imagem será usada como a foto do usuário quando o usuário não tiver nenhuma foto no Microsoft Graph.
Salve todas as suas alterações e reinicie o aplicativo. Agora, o aplicativo deve ter uma aparência muito diferente.
Registrar o aplicativo no portal
Neste exercício, você criará um novo registro de aplicativo Web do Azure AD usando o Azure Active Directory de administração.
Abra um navegador e navegue até o centro de administração do Azure Active Directory. Faça logon usando uma conta pessoal (também conhecida como Conta da Microsoft) ou Conta Corporativa ou de Estudante.
Selecione Azure Active Directory na navegação esquerda e selecione Registros de aplicativos em Gerenciar.
Selecione Novo registro. Na página Registrar um aplicativo, defina os valores da seguinte forma.
- Defina Nome para
Node.js Graph Tutorial
. - Defina Tipos de conta com suporte para Contas em qualquer diretório organizacional e contas pessoais da Microsoft.
- Em URI de Redirecionamento, defina o primeiro menu suspenso para
Web
e defina o valor comohttp://localhost:3000/auth/callback
.
- Defina Nome para
Selecione Registrar. Na página Node.js Graph Tutorial, copie o valor da ID do Aplicativo (cliente) e salve-a, você precisará dela na próxima etapa.
Selecione Certificados e segredos sob Gerenciar. Selecione o botão Novo segredo do cliente. Insira um valor em Descrição e selecione uma das opções para Expira em e selecione Adicionar.
Copie o valor de segredo do cliente antes de sair desta página. Você precisará dele na próxima etapa.
Importante
Este segredo do cliente nunca é mostrado novamente, portanto, certifique-se de copiá-lo agora.
Adicionar autenticação do Azure AD
Neste exercício, você estenderá o aplicativo do exercício anterior para dar suporte à autenticação com o Azure AD. Isso é necessário para obter o token de acesso OAuth necessário para chamar o microsoft Graph. Nesta etapa, você integrará a biblioteca msal-node ao aplicativo.
Crie um novo arquivo chamado .env na raiz do aplicativo e adicione o código a seguir.
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/
Substitua
YOUR_CLIENT_ID_HERE
pela ID do aplicativo do Portal de Registro de Aplicativos e substituaYOUR_CLIENT_SECRET_HERE
pelo segredo do cliente gerado.Importante
Se você estiver usando o controle de origem como git, agora seria um bom momento para excluir o arquivo .env do controle de origem para evitar o vazamento inadvertida da ID do aplicativo e da senha.
Abra ./app.js e adicione a seguinte linha à parte superior do arquivo para carregar o arquivo .env .
require('dotenv').config();
Implementar login
Localize a linha
var app = express();
em ./app.js. Insira o código a seguir após essa linha.// 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);
Esse código inicializa a biblioteca de nós msal com a ID do aplicativo e a senha do aplicativo.
Crie um novo arquivo no diretório ./routes chamado auth.js adicionar o código a seguir.
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;
Isso define um roteador com três rotas:
signin
, ecallback``signout
.A
signin
rota chama agetAuthCodeUrl
função para gerar a URL de logon e redireciona o navegador para essa URL.A
callback
rota é onde o Azure redireciona após a conclusão da assinatura. O código chama a funçãoacquireTokenByCode
para trocar o código de autorização por um token de acesso. Depois que o token é obtido, ele redireciona de volta para a home page com o token de acesso no valor de erro temporário. Vamos usar isso para verificar se nossa assinatura está funcionando antes de seguir em frente. Antes de testarmos, precisamos configurar o aplicativo Express para usar o novo roteador de ./routes/auth.js.O
signout
método registra o usuário e destrói a sessão.Abra ./app.js e insira o código a seguir antes da
var app = express();
linha.const authRouter = require('./routes/auth');
Insira o código a seguir após a
app.use('/', indexRouter);
linha.app.use('/auth', authRouter);
Inicie o servidor e navegue até https://localhost:3000
. Clique no botão entrar e você deve ser redirecionado para https://login.microsoftonline.com
. Faça logon com sua conta da Microsoft e consenta com as permissões solicitadas. O navegador redireciona para o aplicativo, mostrando o token.
Obter detalhes do usuário
Crie um novo arquivo na raiz do projeto chamado graph.js adicionar o código a seguir.
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; }
Isso exporta a função
getUserDetails
, que usa o Microsoft Graph SDK para chamar o/me
ponto de extremidade e retornar o resultado.Abra ./routes/auth.js e adicione as instruções
require
a seguir à parte superior do arquivo.const graph = require('../graph');
Substitua a rota de retorno de chamada existente pelo código a seguir.
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('/'); } );
O novo código salva a ID da conta do usuário na sessão, obtém os detalhes do usuário do Microsoft Graph e salva-o no armazenamento do usuário do aplicativo.
Reinicie o servidor e vá pelo processo de login. Você deve terminar de volta na home page, mas a interface do usuário deve mudar para indicar que você está inscreveu.
Clique no avatar do usuário no canto superior direito para acessar o link Sair . Clicar em Sair redefine a sessão e retorna você para a home page.
Armazenar e atualizar tokens
Neste ponto, seu aplicativo tem um token de acesso, que é enviado no Authorization
header de chamadas da API. Esse é o token que permite que o aplicativo acesse o microsoft Graph em nome do usuário.
No entanto, esse token tem vida curta. O token expira uma hora após a emissão. É aqui que o token de atualização se torna útil. A especificação OAuth introduz um token de atualização, que permite que o aplicativo solicite um novo token de acesso sem exigir que o usuário entre novamente.
Como o aplicativo está usando o pacote msal-node, você não precisa implementar nenhum armazenamento de token ou lógica de atualização. O aplicativo usa o cache padrão de token de memória msal-node, que é suficiente para um aplicativo de exemplo. Os aplicativos de produção devem fornecer seu próprio plug-in de cache para serializar o cache de token em um meio de armazenamento seguro e confiável.
Obter uma exibição de calendário
Neste exercício, você incorporará o Microsoft Graph no aplicativo. Para esse aplicativo, você usará a biblioteca microsoft-graph-client para fazer chamadas para o Microsoft Graph.
Obtenha eventos de calendário do Outlook
Abra ./graph.js e adicione a seguinte função dentro
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; },
Considere o que este código está fazendo.
- O URL que será chamado é
/me/calendarview
. - O
header
método adiciona oPrefer: outlook.timezone
header à solicitação, fazendo com que os horários de início e término sejam retornados no fuso horário do usuário. - O
query
método define osstartDateTime
parâmetros eendDateTime
para o modo de exibição de calendário. - O
select
método limita os campos retornados para cada evento para apenas aqueles que o modo de exibição realmente usará. - O
orderby
método classifica os resultados pela hora de início. - O
top
método limita os resultados a 50 eventos.
- O URL que será chamado é
Crie um novo arquivo no diretório ./routes chamado calendar.js, e adicione o código a seguir.
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;
Atualize ./app.js para usar essa nova rota. Adicione a seguinte linha antes da
var app = express();
linha.const calendarRouter = require('./routes/calendar');
Adicione a seguinte linha após a
app.use('/auth', authRouter);
linha.app.use('/calendar', calendarRouter);
Reiniciar o servidor. Entre e clique no link Calendário na barra de nav. Se tudo funcionar, você deverá ver um despejo JSON de eventos no calendário do usuário.
Exibir os resultados
Agora você pode adicionar um modo de exibição para exibir os resultados de uma maneira mais amigável.
Adicione o código a seguir em ./app.js após a
app.set('view engine', 'hbs');
linha.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'); });
Isso implementa um auxiliar de Guidões para formatar a data ISO 8601 retornada pela Microsoft Graph em algo mais amigável para humanos.
Crie um novo arquivo no diretório ./views chamado calendar.hbs e adicione o código a seguir.
<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>
Isso percorrerá uma coleção de eventos e adicionará uma linha de tabela para cada um.
Agora atualize a rota em ./routes/calendar.js para usar esse exibição. Substitua a rota existente pelo código a seguir.
/* 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); } } );
Salve suas alterações, reinicie o servidor e entre no aplicativo. Clique no link Calendário e o aplicativo deve renderizar uma tabela de eventos.
Criar um novo evento
Nesta seção, você adicionará a capacidade de criar eventos no calendário do usuário.
Criar um novo formulário de evento
Crie um novo arquivo no diretório ./views chamado newevent.hbs e adicione o código a seguir.
<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>
Adicione o código a seguir ao arquivo ./routes/calendar.js antes da
module.exports = router;
linha./* 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'); } } );
Isso implementa um formulário para entrada do usuário e o renderiza.
Criar o evento
Abra ./graph.js e adicione a seguinte função dentro
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); },
Esse código usa os campos de formulário para criar um objeto de evento Graph e envia uma solicitação POST ao ponto de extremidade para criar o evento no calendário
/me/events
padrão do usuário.Adicione o código a seguir ao arquivo ./routes/calendar.js antes da
module.exports = router;
linha./* 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'); } } );
Esse código valida e higieniza a entrada do formulário e chama
graph.createEvent
para criar o evento. Ele redireciona de volta para a exibição de calendário depois que a chamada é concluída.Salve suas alterações e reinicie o aplicativo. Clique no item Dev calendário e clique no botão Criar evento. Preencha os valores e clique em Criar. O aplicativo retorna à exibição de calendário depois que o novo evento é criado.
Parabéns!
Você concluiu o tutorial Node.js microsoft Graph. Agora que você tem um aplicativo de trabalho que chama a Microsoft Graph, você pode experimentar e adicionar novos recursos. Visite a visão geral do microsoft Graph para ver todos os dados que você pode acessar com o Microsoft Graph.
Comentários
Forneça qualquer comentário sobre este tutorial no repositório GitHub .
Tem algum problema com essa seção? Se tiver, envie seus comentários para que possamos melhorar esta seção.