Criar aplicativos no Ruby on Rails com o Microsoft Graph
Este tutorial ensina como criar um aplicativo Web Ruby on Rails 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 início rápido do Ruby para obter o código de trabalho em minutos.
- Baixe ou clone o GitHub repositório.
Pré-requisitos
Antes de iniciar este tutorial, você deve ter as seguintes ferramentas instaladas em sua máquina de desenvolvimento.
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 as seguintes versões das ferramentas necessárias. As etapas neste guia podem funcionar com outras versões, mas que não foram testadas.
- Ruby versão 3.0.1
- SQLite3 versão 3.35.5
- Node.js versão 14.15.0
- Yarn versão 1.22.0
Comentários
Forneça qualquer comentário sobre este tutorial no repositório GitHub.
Criar um aplicativo Web do Ruby on Rails
Neste exercício, você usará Ruby on Rails para criar um aplicativo Web.
Se você ainda não tiver Rails instalado, poderá instalá-lo a partir de sua interface de linha de comando (CLI) com o seguinte comando.
gem install rails -v 6.1.3.1
Abra sua CLI, navegue até um diretório onde você tem direitos para criar arquivos e execute o seguinte comando para criar um novo aplicativo Rails.
rails new graph-tutorial
Navegue até esse novo diretório e insira o seguinte comando para iniciar um servidor Web local.
rails server
Abra o navegador e vá até
http://localhost:3000
. Se tudo estiver funcionando, você verá um "Yay! Você está em Rails!" Mensagem. Se você não vir essa mensagem, verifique o guia Rails getting started.
Instalar gemas
Antes de continuar, instale algumas gemas adicionais que você usará posteriormente:
- omniauth-oauth2 para manipular fluxos de tokens OAuth e de entrada.
- omniauth-rails_csrf_protection para adicionar proteção CSRF ao OmniAuth.
- httparty para fazer chamadas para o Microsoft Graph.
- activerecord-session_store para armazenar sessões no banco de dados.
Abra ./Gemfile e adicione as seguintes linhas.
# OAuth gem 'omniauth-oauth2', '~> 1.7.1' # OmniAuth CSRF protection gem 'omniauth-rails_csrf_protection', '~> 1.0.0' # REST calls to Microsoft Graph gem 'httparty', '~> 0.18.1' # Session storage in database gem 'activerecord-session_store', '~> 2.0.0'
Em sua CLI, execute o seguinte comando.
bundle install
Em sua CLI, execute os seguintes comandos para configurar o banco de dados para armazenar sessões.
rails generate active_record:session_migration rake db:migrate
Crie um novo arquivo chamado
session_store.rb
no diretório ./config/initializers e adicione o código a seguir.Rails.application.config.session_store :active_record_store, :key => '_graph_app_session'
Design do aplicativo
Nesta seção, você criará a interface do usuário básica do aplicativo.
Abra ./app/views/layouts/application.html.erb e substitua seu conteúdo pelo seguinte.
<!DOCTYPE html> <html> <head> <title>Ruby Graph Tutorial</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <link rel="shortcut icon" href="favicon.png"/> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> <link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/11.0.0/css/fabric.min.css"/> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> </head> <body> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <%= link_to "Ruby Graph Tutorial", root_path, class: "navbar-brand" %> <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"> <%= link_to "Home", root_path, class: "nav-link#{' active' if controller.controller_name == 'home'}" %> </li> <% if @user_name %> <li class="nav-item" data-turbolinks="false"> <%= link_to "Calendar", "/calendar", class: "nav-link#{' active' if controller.controller_name == 'calendar'}" %> </li> <% end %> </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> <a class="nav-link external-link" href="https://developer.microsoft.com/graph/docs/concepts/overview" target="_blank"> <i class="ms-Icon ms-Icon--NavigateExternalInline mr-1"></i>Docs </a> </li> <% if @user_name %> <li class="nav-item dropdown"> <a class="nav-link avatar-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 profile-photo"> <% else %> <%= image_tag "no-profile-photo.png", class: "rounded-circle align-self-center mr-2 profile-photo" %> <% end %> </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0"><%= @user_name %></h5> <p class="dropdown-item-text text-muted mb-0"><%= @user_email %></p> <div class="dropdown-divider"></div> <%= link_to "Sign Out", {:controller => :auth, :action => :signout}, :class => "dropdown-item" %> </div> </li> <% else %> <li class="nav-item"> <%= link_to "Sign In", "/auth/microsoft_graph_auth", method: :post, class: "nav-link" %> </li> <% end %> </ul> </div> </div> </nav> <main role="main" class="container"> <% if @errors %> <% @errors.each do |error| %> <div class="alert alert-danger" role="alert"> <p class="mb-3"><%= error[:message] %></p> <%if error[:debug] %> <pre class="alert-pre border bg-light p-2"><code><%= error[:debug] %></code></pre> <% end %> </div> <% end %> <% end %> <%= yield %> </main> <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/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </body> </html>
Este código adiciona Bootstrap para estilo simples e Fabric Core para alguns ícones simples. Ele também define um layout global com uma barra de nav.
Abra ./app/assets/stylesheets/application.css e adicione o seguinte ao final do arquivo.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; } .external-link { padding-top: 6px; } .avatar-link { padding-top: 4px; padding-bottom: 4px; } .profile-photo { width: 32px; }
Gere um controlador de home page com o seguinte comando.
rails generate controller Home index
Configure a
index
ação no controlador como a página padrão doHome
aplicativo. Abra ./config/routes.rb e substitua seu conteúdo pelo seguinteRails.application.routes.draw do get 'home/index' root 'home#index' # Add future routes here end
Abra ./app/view/home/index.html.erb e substitua seu conteúdo pelo seguinte.
<div class="jumbotron"> <h1>Ruby Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from Ruby</p> <% if @user_name %> <h4>Welcome <%= @user_name %>!</h4> <p>Use the navigation bar at the top of the page to get started.</p> <% else %> <%= link_to "Click here to sign in", "/auth/microsoft_graph_auth", method: :post, class: "btn btn-primary btn-large" %> <% end %> </div>
Adicione um arquivo PNG chamado no-profile-photo.png no diretório ./app/assets/images.
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
Ruby 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/microsoft_graph_auth/callback
.
- Defina Nome para
Selecione Registrar. Na página Tutorial Graph Ruby, 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 gem omniauth-oauth2 ao aplicativo e criará uma estratégia OmniAuth personalizada.
Crie um arquivo separado para manter sua ID do aplicativo e seu segredo. Crie um novo arquivo
oauth_environment_variables.rb
chamado na pasta ./config e adicione o código a seguir.ENV['AZURE_APP_ID'] = 'YOUR_APP_ID_HERE' ENV['AZURE_APP_SECRET'] = 'YOUR_APP_SECRET_HERE' ENV['AZURE_SCOPES'] = 'openid profile email offline_access user.read mailboxsettings.read calendars.readwrite'
Substitua pela ID do aplicativo do Portal de
YOUR_APP_ID_HERE
Registro de Aplicativos e substitua pela senhaYOUR_APP_SECRET_HERE
gerada.Importante
Se você estiver usando o controle de origem, como git, agora seria um bom momento para excluir o arquivo do controle de origem para evitar o vazamento inadvertida da ID do aplicativo e da
oauth_environment_variables.rb
senha.Abra ./config/environment.rb e adicione o seguinte código antes da
Rails.application.initialize!
linha.# Load the Rails application. require_relative "application" # Load OAuth settings oauth_environment_variables = File.join(Rails.root, 'config', 'oauth_environment_variables.rb') load(oauth_environment_variables) if File.exist?(oauth_environment_variables) # Initialize the Rails application. Rails.application.initialize!
Setup OmniAuth
Você já instalou a gema, mas para fazê-la funcionar com os pontos de extremidade do Azure OAuth, você precisa criar uma estratégia omniauth-oauth2
OAuth2. Esta é uma classe Ruby que define os parâmetros para fazer solicitações OAuth para o provedor do Azure.
Crie um novo arquivo
microsoft_graph_auth.rb
chamado na pasta ./lib'** e adicione o código a seguir.require 'omniauth-oauth2' module OmniAuth module Strategies # Implements an OmniAuth strategy to get a Microsoft Graph # compatible token from Azure AD class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2 option :name, :microsoft_graph_auth DEFAULT_SCOPE = 'openid email profile User.Read'.freeze # Configure the Microsoft identity platform endpoints option :client_options, :site => 'https://login.microsoftonline.com', :authorize_url => '/common/oauth2/v2.0/authorize', :token_url => '/common/oauth2/v2.0/token' # Send the scope parameter during authorize option :authorize_options, [:scope] # Unique ID for the user is the id field uid { raw_info['id'] } # Get additional information after token is retrieved extra do { 'raw_info' => raw_info } end def raw_info # Get user profile information from the /me endpoint @raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me?$select=displayName,mail,mailboxSettings,userPrincipalName').parsed end def authorize_params super.tap do |params| params[:scope] = request.params['scope'] if request.params['scope'] params[:scope] ||= DEFAULT_SCOPE end end # Override callback URL # OmniAuth by default passes the entire URL of the callback, including # query parameters. Azure fails validation because that doesn't match the # registered callback. def callback_url options[:redirect_uri] || (full_host + script_name + callback_path) end end end end
Aproveite um momento para revisar o que esse código faz.
- Ele define o
client_options
para especificar os plataforma de identidade da Microsoft de extremidade. - Especifica que o parâmetro
scope
deve ser enviado durante a fase de autorização. - Ele mapeia
id
a propriedade do usuário como a ID exclusiva do usuário. - Ele usa o token de acesso para recuperar o perfil do usuário do Microsoft Graph preencher o
raw_info
hash. - Ele substitui a URL de retorno de chamada para garantir que ela corresponde ao retorno de chamada registrado no portal de registro do aplicativo.
- Ele define o
Crie um novo arquivo
omniauth_graph.rb
chamado na pasta ./config/initializers e adicione o código a seguir.require 'microsoft_graph_auth' Rails.application.config.middleware.use OmniAuth::Builder do provider :microsoft_graph_auth, ENV['AZURE_APP_ID'], ENV['AZURE_APP_SECRET'], :scope => ENV['AZURE_SCOPES'] end
Esse código será executado quando o aplicativo for iniciado. Ele carrega o middleware OmniAuth com o provedor, configurado com as variáveis de ambiente
microsoft_graph_auth
definidas em oauth_environment_variables.rb.
Implementar login
Agora que o middleware OmniAuth está configurado, você pode continuar adicionando login ao aplicativo.
Execute o seguinte comando em sua CLI para gerar um controlador para entrar e sair.
rails generate controller Auth
Abra ./app/controllers/auth_controller.rb. Adicione um método de retorno de chamada à
AuthController
classe. Esse método será chamado pelo middleware OmniAuth depois que o fluxo OAuth for concluído.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Temporary for testing! render json: data.to_json end
Por enquanto, tudo o que isso faz é renderizar o hash fornecido pelo OmniAuth. Você usará isso para verificar se a assinatura está funcionando antes de seguir em frente.
Adicione as rotas a ./config/routes.rb.
# Add route for OmniAuth callback match '/auth/:provider/callback', :to => 'auth#callback', :via => [:get, :post]
Inicie o servidor e navegue até
https://localhost:3000
. Clique no botão entrar e você deve ser redirecionado parahttps://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 hash gerado pelo OmniAuth.{ "provider": "microsoft_graph_auth", "uid": "eb52b3b2-c4ac-4b4f-bacd-d5f7ece55df0", "info": { "name": null }, "credentials": { "token": "eyJ0eXAi...", "refresh_token": "OAQABAAA...", "expires_at": 1529517383, "expires": true }, "extra": { "raw_info": { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,mail,mailboxSettings,userPrincipalName)/$entity", "displayName": "Lynne Robbins", "mail": "LynneR@contoso.OnMicrosoft.com", "userPrincipalName": "LynneR@contoso.OnMicrosoft.com", "id": "d294e784-840e-4f9f-bb1e-95c0a75f2f18@2d18179c-4386-4cbd-8891-7fd867c4f62e", "mailboxSettings": { "archiveFolder": "AAMkAGI2...", "timeZone": "Pacific Standard Time", "delegateMeetingMessageDeliveryOptions": "sendToDelegateOnly", "dateFormat": "M/d/yyyy", "timeFormat": "h:mm tt", "automaticRepliesSetting": { "status": "disabled", "externalAudience": "all", "internalReplyMessage": "", "externalReplyMessage": "", "scheduledStartDateTime": { "dateTime": "2020-12-09T17:00:00.0000000", "timeZone": "UTC" }, "scheduledEndDateTime": { "dateTime": "2020-12-10T17:00:00.0000000", "timeZone": "UTC" } }, "language": { "locale": "en-US", "displayName": "English (United States)" }, "workingHours": { "daysOfWeek": [ "monday", "tuesday", "wednesday", "thursday", "friday" ], "startTime": "08:00:00.0000000", "endTime": "17:00:00.0000000", "timeZone": { "name": "Pacific Standard Time" } } } } } }
Armazenando os tokens
Agora que você pode obter tokens, é hora de implementar uma maneira de armazená-los no aplicativo. Como este é um aplicativo de exemplo, para simplificar, você os armazenará na sessão. Um aplicativo do mundo real usaria uma solução de armazenamento seguro mais confiável, como um banco de dados.
Abra ./app/controllers/application_controller.rb. Adicione o método a seguir à classe
ApplicationController
.def save_in_session(auth_hash) # Save the token info session[:graph_token_hash] = auth_hash[:credentials] # Save the user's display name session[:user_name] = auth_hash.dig(:extra, :raw_info, :displayName) # Save the user's email address # Use the mail field first. If that's empty, fall back on # userPrincipalName session[:user_email] = auth_hash.dig(:extra, :raw_info, :mail) || auth_hash.dig(:extra, :raw_info, :userPrincipalName) # Save the user's time zone session[:user_timezone] = auth_hash.dig(:extra, :raw_info, :mailboxSettings, :timeZone) end
O método assume o hash OmniAuth como um parâmetro e extrai os bits relevantes de informações e armazena isso na sessão.
Adicione funções de acessador à classe para recuperar o nome de usuário, o endereço de email e o token de acesso de
ApplicationController
volta da sessão.def user_name session[:user_name] end def user_email session[:user_email] end def user_timezone session[:user_timezone] end def access_token session[:graph_token_hash][:token] end
Adicione o código a seguir
ApplicationController
à classe que será executado antes de qualquer ação ser processada.before_action :set_user def set_user @user_name = user_name @user_email = user_email end
Este método define as variáveis que o layout (emapplication.html.erb) usa para mostrar as informações do usuário na barra de nav. Ao adicioná-lo aqui, você não precisa adicionar esse código em cada ação de controlador único. No entanto, isso também será executado para ações no
AuthController
, o que não é ideal.Adicione o código a seguir à
AuthController
classe em ./app/controllers/auth_controller.rb para ignorar a ação anterior.skip_before_action :set_user
Atualize a função na classe para armazenar os tokens na
callback
sessão eAuthController
redirecionar de volta para a página principal. Substitua a funçãocallback
existente pela seguinte.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Save the data in the session save_in_session data redirect_to root_url end
Implementar a saída
Antes de testar esse novo recurso, adicione uma maneira de sair.
Adicione a ação a seguir à
AuthController
classe.def signout reset_session redirect_to root_url end
Adicione essa ação a ./config/routes.rb.
get 'auth/signout'
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.
Tokens de atualização
Se você observar de perto o hash gerado pelo OmniAuth, perceberá que há dois tokens no hash: token
e refresh_token
. O valor em token
é o 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 refresh_token
valor se torna útil. O token de atualização permite que o aplicativo solicite um novo token de acesso sem exigir que o usuário faça login novamente. Atualize o código de gerenciamento de token para implementar a atualização de token.
Abra ./app/controllers/application_controller.rb e adicione as seguintes
require
instruções na parte superior:require 'microsoft_graph_auth' require 'oauth2'
Adicione o método a seguir à classe
ApplicationController
.def refresh_tokens(token_hash) oauth_strategy = OmniAuth::Strategies::MicrosoftGraphAuth.new( nil, ENV['AZURE_APP_ID'], ENV['AZURE_APP_SECRET'] ) token = OAuth2::AccessToken.new( oauth_strategy.client, token_hash[:token], :refresh_token => token_hash[:refresh_token] ) # Refresh the tokens new_tokens = token.refresh!.to_hash.slice(:access_token, :refresh_token, :expires_at) # Rename token key new_tokens[:token] = new_tokens.delete :access_token # Store the new hash session[:graph_token_hash] = new_tokens end
Este método usa a gem oauth2 (uma dependência da gema) para atualizar
omniauth-oauth2
os tokens e atualiza a sessão.Substitua o método
access_token
atual pelo seguinte.def access_token token_hash = session[:graph_token_hash] # Get the expiry time - 5 minutes expiry = Time.at(token_hash[:expires_at] - 300) if Time.now > expiry # Token expired, refresh new_hash = refresh_tokens token_hash new_hash[:token] else token_hash[:token] end end
Em vez de apenas retornar o token da sessão, ele primeiro verificará se ele está perto de expirar. Se estiver, ele será atualizado antes de retornar o token.
Obter um modo de exibição de calendário
Neste exercício, você incorporará o microsoft Graph no aplicativo. Para este aplicativo, você usará a gema httparty para fazer chamadas para o Microsoft Graph.
Criar um Graph auxiliar
Crie um auxiliar para gerenciar todas as chamadas da API. Execute o seguinte comando em sua CLI para gerar o auxiliar.
rails generate helper Graph
Abra ./app/helpers/graph_helper.rb e substitua o conteúdo pelo seguinte.
require 'httparty' # Graph API helper methods module GraphHelper GRAPH_HOST = 'https://graph.microsoft.com'.freeze def make_api_call(method, endpoint, token, headers = nil, params = nil, payload = nil) headers ||= {} headers[:Authorization] = "Bearer #{token}" headers[:Accept] = 'application/json' params ||= {} case method.upcase when 'GET' HTTParty.get "#{GRAPH_HOST}#{endpoint}", :headers => headers, :query => params when 'POST' headers['Content-Type'] = 'application/json' HTTParty.post "#{GRAPH_HOST}#{endpoint}", :headers => headers, :query => params, :body => payload ? payload.to_json : nil else raise "HTTP method #{method.upcase} not implemented" end end end
Aproveite um momento para revisar o que esse código faz. Ele faz uma solicitação GET ou POST simples por meio da httparty
gema para o ponto de extremidade solicitado. Ele envia o token de acesso no header e inclui todos os parâmetros de consulta Authorization
que são passados.
Por exemplo, para usar o make_api_call
método para fazer um GET para , você poderia https://graph.microsoft.com/v1.0/me?$select=displayName
chamá-lo assim:
make_api_call 'GET', '/v1.0/me', access_token, {}, { '$select': 'displayName' }
Você criará isso mais tarde à medida que implementar mais recursos Graph Microsoft no aplicativo.
Obtenha eventos de calendário do Outlook
Em sua CLI, execute o seguinte comando para adicionar um novo controlador.
rails generate controller Calendar index new
Adicione a nova rota a ./config/routes.rb.
get 'calendar', :to => 'calendar#index'
Adicione um novo método ao Graph auxiliar para obter um modo de exibição de calendário. Abra ./app/helpers/graph_helper.rb e adicione o método a seguir ao
GraphHelper
módulo.def get_calendar_view(token, start_datetime, end_datetime, timezone) get_events_url = '/v1.0/me/calendarview' headers = { 'Prefer' => "outlook.timezone=\"#{timezone}\"" } query = { 'startDateTime' => start_datetime.iso8601, 'endDateTime' => end_datetime.iso8601, '$select' => 'subject,organizer,start,end', '$orderby' => 'start/dateTime', '$top' => 50 } response = make_api_call 'GET', get_events_url, token, headers, query raise response.parsed_response.to_s || "Request returned #{response.code}" unless response.code == 200 response.parsed_response['value'] end
Considere o que este código está fazendo.
- O URL que será chamado é
/v1.0/me/calendarview
.- O header faz com que os horários de início e término nos resultados sejam ajustados ao
Prefer: outlook.timezone
fuso horário do usuário. - Os
startDateTime
endDateTime
parâmetros e definirão o início e o fim do exibição. - O
$select
parâmetro limita os campos retornados para cada evento para apenas aqueles que o exibição realmente usará. - O
$orderby
parâmetro classifica os resultados por hora de início. - O
$top
parâmetro limita os resultados a 50 eventos.
- O header faz com que os horários de início e término nos resultados sejam ajustados ao
- Para uma resposta bem-sucedida, ele retorna a matriz de itens contidos na
value
chave.
- O URL que será chamado é
Adicione um novo método ao Graph auxiliar para procurar um identificador de fuso horário IANA com base em um Windows de fuso horário. Isso é necessário porque a Microsoft Graph pode retornar fusos horário como Windows nomes de fuso horário, e a classe Ruby DateTime requer identificadores de fuso horário IANA.
TIME_ZONE_MAP = { 'Dateline Standard Time' => 'Etc/GMT+12', 'UTC-11' => 'Etc/GMT+11', 'Aleutian Standard Time' => 'America/Adak', 'Hawaiian Standard Time' => 'Pacific/Honolulu', 'Marquesas Standard Time' => 'Pacific/Marquesas', 'Alaskan Standard Time' => 'America/Anchorage', 'UTC-09' => 'Etc/GMT+9', 'Pacific Standard Time (Mexico)' => 'America/Tijuana', 'UTC-08' => 'Etc/GMT+8', 'Pacific Standard Time' => 'America/Los_Angeles', 'US Mountain Standard Time' => 'America/Phoenix', 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 'Mountain Standard Time' => 'America/Denver', 'Central America Standard Time' => 'America/Guatemala', 'Central Standard Time' => 'America/Chicago', 'Easter Island Standard Time' => 'Pacific/Easter', 'Central Standard Time (Mexico)' => 'America/Mexico_City', 'Canada Central Standard Time' => 'America/Regina', 'SA Pacific Standard Time' => 'America/Bogota', 'Eastern Standard Time (Mexico)' => 'America/Cancun', 'Eastern Standard Time' => 'America/New_York', 'Haiti Standard Time' => 'America/Port-au-Prince', 'Cuba Standard Time' => 'America/Havana', 'US Eastern Standard Time' => 'America/Indianapolis', 'Turks And Caicos Standard Time' => 'America/Grand_Turk', 'Paraguay Standard Time' => 'America/Asuncion', 'Atlantic Standard Time' => 'America/Halifax', 'Venezuela Standard Time' => 'America/Caracas', 'Central Brazilian Standard Time' => 'America/Cuiaba', 'SA Western Standard Time' => 'America/La_Paz', 'Pacific SA Standard Time' => 'America/Santiago', 'Newfoundland Standard Time' => 'America/St_Johns', 'Tocantins Standard Time' => 'America/Araguaina', 'E. South America Standard Time' => 'America/Sao_Paulo', 'SA Eastern Standard Time' => 'America/Cayenne', 'Argentina Standard Time' => 'America/Buenos_Aires', 'Greenland Standard Time' => 'America/Godthab', 'Montevideo Standard Time' => 'America/Montevideo', 'Magallanes Standard Time' => 'America/Punta_Arenas', 'Saint Pierre Standard Time' => 'America/Miquelon', 'Bahia Standard Time' => 'America/Bahia', 'UTC-02' => 'Etc/GMT+2', 'Azores Standard Time' => 'Atlantic/Azores', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'UTC' => 'Etc/GMT', 'GMT Standard Time' => 'Europe/London', 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Sao Tome Standard Time' => 'Africa/Sao_Tome', 'Morocco Standard Time' => 'Africa/Casablanca', 'W. Europe Standard Time' => 'Europe/Berlin', 'Central Europe Standard Time' => 'Europe/Budapest', 'Romance Standard Time' => 'Europe/Paris', 'Central European Standard Time' => 'Europe/Warsaw', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'Jordan Standard Time' => 'Asia/Amman', 'GTB Standard Time' => 'Europe/Bucharest', 'Middle East Standard Time' => 'Asia/Beirut', 'Egypt Standard Time' => 'Africa/Cairo', 'E. Europe Standard Time' => 'Europe/Chisinau', 'Syria Standard Time' => 'Asia/Damascus', 'West Bank Standard Time' => 'Asia/Hebron', 'South Africa Standard Time' => 'Africa/Johannesburg', 'FLE Standard Time' => 'Europe/Kiev', 'Israel Standard Time' => 'Asia/Jerusalem', 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 'Sudan Standard Time' => 'Africa/Khartoum', 'Libya Standard Time' => 'Africa/Tripoli', 'Namibia Standard Time' => 'Africa/Windhoek', 'Arabic Standard Time' => 'Asia/Baghdad', 'Turkey Standard Time' => 'Europe/Istanbul', 'Arab Standard Time' => 'Asia/Riyadh', 'Belarus Standard Time' => 'Europe/Minsk', 'Russian Standard Time' => 'Europe/Moscow', 'E. Africa Standard Time' => 'Africa/Nairobi', 'Iran Standard Time' => 'Asia/Tehran', 'Arabian Standard Time' => 'Asia/Dubai', 'Astrakhan Standard Time' => 'Europe/Astrakhan', 'Azerbaijan Standard Time' => 'Asia/Baku', 'Russia Time Zone 3' => 'Europe/Samara', 'Mauritius Standard Time' => 'Indian/Mauritius', 'Saratov Standard Time' => 'Europe/Saratov', 'Georgian Standard Time' => 'Asia/Tbilisi', 'Volgograd Standard Time' => 'Europe/Volgograd', 'Caucasus Standard Time' => 'Asia/Yerevan', 'Afghanistan Standard Time' => 'Asia/Kabul', 'West Asia Standard Time' => 'Asia/Tashkent', 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 'Pakistan Standard Time' => 'Asia/Karachi', 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', 'India Standard Time' => 'Asia/Calcutta', 'Sri Lanka Standard Time' => 'Asia/Colombo', 'Nepal Standard Time' => 'Asia/Katmandu', 'Central Asia Standard Time' => 'Asia/Almaty', 'Bangladesh Standard Time' => 'Asia/Dhaka', 'Omsk Standard Time' => 'Asia/Omsk', 'Myanmar Standard Time' => 'Asia/Rangoon', 'SE Asia Standard Time' => 'Asia/Bangkok', 'Altai Standard Time' => 'Asia/Barnaul', 'W. Mongolia Standard Time' => 'Asia/Hovd', 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 'Tomsk Standard Time' => 'Asia/Tomsk', 'China Standard Time' => 'Asia/Shanghai', 'North Asia East Standard Time' => 'Asia/Irkutsk', 'Singapore Standard Time' => 'Asia/Singapore', 'W. Australia Standard Time' => 'Australia/Perth', 'Taipei Standard Time' => 'Asia/Taipei', 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Aus Central W. Standard Time' => 'Australia/Eucla', 'Transbaikal Standard Time' => 'Asia/Chita', 'Tokyo Standard Time' => 'Asia/Tokyo', 'North Korea Standard Time' => 'Asia/Pyongyang', 'Korea Standard Time' => 'Asia/Seoul', 'Yakutsk Standard Time' => 'Asia/Yakutsk', 'Cen. Australia Standard Time' => 'Australia/Adelaide', 'AUS Central Standard Time' => 'Australia/Darwin', 'E. Australia Standard Time' => 'Australia/Brisbane', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Tasmania Standard Time' => 'Australia/Hobart', 'Vladivostok Standard Time' => 'Asia/Vladivostok', 'Lord Howe Standard Time' => 'Australia/Lord_Howe', 'Bougainville Standard Time' => 'Pacific/Bougainville', 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 'Magadan Standard Time' => 'Asia/Magadan', 'Norfolk Standard Time' => 'Pacific/Norfolk', 'Sakhalin Standard Time' => 'Asia/Sakhalin', 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 'Russia Time Zone 11' => 'Asia/Kamchatka', 'New Zealand Standard Time' => 'Pacific/Auckland', 'UTC+12' => 'Etc/GMT-12', 'Fiji Standard Time' => 'Pacific/Fiji', 'Chatham Islands Standard Time' => 'Pacific/Chatham', 'UTC+13' => 'Etc/GMT-13', 'Tonga Standard Time' => 'Pacific/Tongatapu', 'Samoa Standard Time' => 'Pacific/Apia', 'Line Islands Standard Time' => 'Pacific/Kiritimati' }.freeze def get_iana_from_windows(windows_tz_name) iana = TIME_ZONE_MAP[windows_tz_name] # If no mapping found, assume the supplied # value was already an IANA identifier iana || windows_tz_name end
Abra ./app/controllers/calendar_controller.rb e substitua todo o conteúdo pelo seguinte.
# Calendar controller class CalendarController < ApplicationController include GraphHelper def index # Get the IANA identifier of the user's time zone time_zone = get_iana_from_windows(user_timezone) # Calculate the start and end of week in the user's time zone start_datetime = Date.today.beginning_of_week(:sunday).in_time_zone(time_zone).to_time end_datetime = start_datetime.advance(:days => 7) @events = get_calendar_view access_token, start_datetime, end_datetime, user_timezone || [] render json: @events rescue RuntimeError => e @errors = [ { :message => 'Microsoft Graph returned an error getting events.', :debug => e } ] end end
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 HTML para exibir os resultados de uma maneira mais amigável.
Abra ./app/views/calendar/index.html.erb e substitua seu conteúdo pelo seguinte.
<h1>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> <% @events.each do |event| %> <tr> <td><%= event['organizer']['emailAddress']['name'] %></td> <td><%= event['subject'] %></td> <td><%= event['start']['dateTime'].to_time(:utc).strftime('%-m/%-d/%Y %l:%M %p') %></td> <td><%= event['end']['dateTime'].to_time(:utc).strftime('%-m/%-d/%Y %l:%M %p') %></td> </tr> <% end %> </tbody> </table>
Isso percorrerá uma coleção de eventos e adicionará uma linha de tabela para cada um.
Remova a
render json: @events
linha da ação emindex
./app/controllers/calendar_controller.rb.Atualize a página 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.
Abra ./app/helpers/graph_helper.rb e adicione o método a seguir à classe Graph.
def create_event(token, timezone, subject, start_datetime, end_datetime, attendees, body) create_event_url = '/v1.0/me/events' # Create an event object # https://docs.microsoft.com/graph/api/resources/event?view=graph-rest-1.0 new_event = { 'subject' => subject, 'start' => { 'dateTime' => start_datetime, 'timeZone' => timezone }, 'end' => { 'dateTime' => end_datetime, 'timeZone' => timezone } } unless attendees.empty? attendee_array = [] # Create an attendee object # https://docs.microsoft.com/graph/api/resources/attendee?view=graph-rest-1.0 attendees.each { |email| attendee_array.push({ 'type' => 'required', 'emailAddress' => { 'address' => email } }) } new_event['attendees'] = attendee_array end unless body.empty? # Create an itemBody object # https://docs.microsoft.com/graph/api/resources/itembody?view=graph-rest-1.0 new_event['body'] = { 'contentType' => 'text', 'content' => body } end response = make_api_call 'POST', create_event_url, token, nil, nil, new_event raise response.parsed_response.to_s || "Request returned #{response.code}" unless response.code == 201 end
Abra ./app/controllers/calendar_controller e adicione a seguinte rota à classe CalendarController.
def create # Semicolon-delimited list, split to an array attendees = params[:ev_attendees].split(';') # Create the event create_event access_token, user_timezone, params[:ev_subject], params[:ev_start], params[:ev_end], attendees, params[:ev_body] # Redirect back to the calendar list redirect_to({ :action => 'index' }) rescue RuntimeError => e @errors = [ { :message => 'Microsoft Graph returned an error creating the event.', :debug => e } ] end
Abra ./config/routes.rb e adicione a nova rota.
post 'calendar/new', :to => 'calendar#create'
Abra ./app/views/calendar/new.html.erb e substitua seu conteúdo pelo seguinte.
<h1>New event</h1> <%= form_with url: "/calendar/new" do |form| %> <div class="form-group"> <%= form.label :ev_subject, "Subject" %> <%= form.text_field :ev_subject, class: "form-control", required: true %> </div> <div class="form-group"> <%= form.label :ev_attendees, "Attendees" %> <%= form.text_field :ev_attendees, class: "form-control", placeholder: "Separate multiple email addresses with a semicolon (';')" %> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <%= form.label :ev_start, "Start" %> <%= form.datetime_local_field :ev_start, class: "form-control", required: true %> </div> </div> <div class="col"> <div class="form-group"> <%= form.label :ev_end, "End" %> <%= form.datetime_local_field :ev_end, class: "form-control", required: true %> </div> </div> </div> <div class="form-group mb-3"> <%= form.label :ev_body, "Body" %> <%= form.text_area :ev_body, class: "form-control", rows: "3" %> </div> <%= form.submit "Create", class: "btn btn-primary mr-2" %> <a class="btn btn-secondary" href="/calendar">Cancel</a> <% end %>
Salve suas alterações e atualize o aplicativo. Na página Calendário, selecione o botão Novo evento. Preencha o formulário e selecione Criar para criar um novo evento.
Parabéns!
Você concluiu o tutorial de Graph Ruby Microsoft. 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.