2017年1月11日水曜日

Office365 のカレンダー API を使って予定の登録を行ってみた

概要

Office365 のカレンダー API を実行して予定の登録を行ってみました
前回の予定を取得するアプリを拡張しています

環境

  • Windows7 64bit
  • Ruby 2.2.4p230
  • gem 2.4.8
  • Rails 5.0.0
  • Office365 API v2.0 (2016/08/18 時点)

MS アプリケーションポータルでアプリの作成

前回を参考にして作成してください
「アプリケーション ID」と「アプリケーションシークレット」の 2 つを取得できれば OK です

Rails アプリの作成

  • rails new o365-tutorial
  • cd o365-tutorial
  • vim Gemfile
gem 'json'
gem 'oauth2'
gem 'activerecord-session_store'
gem 'faraday'

を追記して

  • bundle install
  • rails generate active_record:session_migration
  • rails db:migrate

コーディングしていく

トップ画面の作成

  • cd o365-tutorial
  • vim app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include AuthHelper

  def home
    login_url = get_login_url
    render html: "<a href='#{login_url}'>Log in and view my calendar</a>".html_safe
  end
end
  • vim config/routes.rb
Rails.application.routes.draw do
  root 'application#home'
  get 'authorize' => 'auth#gettoken'
  get 'calendar/index'
end

認証部分の作成

  • cd o365-tutorial
  • rails generate controller Auth
  • vim app/helpers/auth_helper.rb
module AuthHelper
  CLIENT_ID = 'be0c3ce7-1379-45b9-830a-9960c8656b30'
  CLIENT_SECRET = 'Tkp******************************'

  SCOPES = [ 'openid',
             'offline_access',
             'https://outlook.office.com/calendars.readwrite' ]

  def get_login_url
    client = OAuth2::Client.new(CLIENT_ID,
                                CLIENT_SECRET,
                                :site => 'https://login.microsoftonline.com',
                                :authorize_url => '/common/oauth2/v2.0/authorize',
                                :token_url => '/common/oauth2/v2.0/token')

    login_url = client.auth_code.authorize_url(:redirect_uri => authorize_url, :scope => SCOPES.join(' '))
  end

  def get_token_from_code(auth_code)
    client = OAuth2::Client.new(CLIENT_ID,
                              CLIENT_SECRET,
                              :site => 'https://login.microsoftonline.com',
                              :authorize_url => '/common/oauth2/v2.0/authorize',
                              :token_url => '/common/oauth2/v2.0/token')

    token = client.auth_code.get_token(auth_code,
                                     :redirect_uri => authorize_url,
                                     :scope => SCOPES.join(' '))
  end

  def get_user_email(access_token)
    conn = Faraday.new(:url => 'https://outlook.office.com') do |faraday|
      faraday.response :logger
      faraday.adapter  Faraday.default_adapter  
    end

    response = conn.get do |request|
      request.url 'api/v2.0/Me'
      request.headers['Authorization'] = "Bearer #{access_token}"
      request.headers['Accept'] = 'application/json'
    end

    email = JSON.parse(response.body)['EmailAddress']
  end

  def get_access_token(token_hash)
    client = OAuth2::Client.new(CLIENT_ID,
                                CLIENT_SECRET,
                                :site => 'https://login.microsoftonline.com',
                                :authorize_url => '/common/oauth2/v2.0/authorize',
                                :token_url => '/common/oauth2/v2.0/token')

    token = OAuth2::AccessToken.from_hash(client, token_hash)

    if token.expired?
      new_token = token.refresh!
      session[:azure_token] = new_token.to_hash
      access_token = new_token.token
    else
      access_token = token.token
    end
  end
end

CLIENT_ID, CLIENT_SECRET の部分はアプリポータルで取得したアプリケーション ID とアプリケーションシークレットを入力してください
権限としてカレンダーの取得と登録ができる「calendars.readwrite」を指定します
これを指定しないと API を実行しても 401 エラーが返却され続けてしまいます
しかも 401 エラーはメッセージボディを含んでいないので何が原因でエラーになっているかわからないので注意しましょう

  • vim app/controllers/auth_controller.rb
class AuthController < ApplicationController

  $token = {}

  def gettoken
    token = get_token_from_code params[:code]
    $token = token.to_hash
    session[:user_email] = get_user_email token.token
    redirect_to calendar_index_url
  end
end

カレンダーの一覧を表示する部分の作成

  • cd o365-tutorial
  • rails generate controller Calendar index
  • vim app/controllers/calendar_controller.rb
class CalendarController < ApplicationController
  include AuthHelper

  def index
    token = get_access_token($token)
    email = session[:user_email]
    if token
      conn = Faraday.new(:url => 'https://outlook.office.com') do |faraday|
        faraday.response :logger
        faraday.adapter  Faraday.default_adapter  
      end
      response = conn.get do |request|
        request.url '/api/v2.0/Me/Events?$orderby=Start/DateTime desc&$select=Subject,Start,End&$top=10'
        request.headers['Authorization'] = "Bearer #{token}"
        request.headers['Accept'] = "application/json"
        request.headers['X-AnchorMailbox'] = email
      end
      #puts JSON.pretty_generate(JSON.parse(response.body))
      @events = JSON.parse(response.body)['value']
    else
      redirect_to root_url
    end
  end

  def create
    token = get_access_token($token)
    email = session[:user_email]
    if token
      conn = Faraday.new(:url => 'https://outlook.office.com') do |faraday|
        faraday.response :logger
        faraday.adapter  Faraday.default_adapter  
      end
      response = conn.post do |req|
        req.headers['Authorization'] = "Bearer #{token}"
        req.headers['Content-Type'] = 'application/json'
        req.url '/api/v2.0/Me/Events'
        req.body = {
          :Subject => params['subject'],
          :Body => {
            :ContentType => "HTML",
            :Content => "I think it will meet our requirements!"
          },
          :Start => {
            :DateTime => params['start'],
            :TimeZone => "Tokyo Standard Time"
          },
          :End => {
            :DateTime => params['end'],
            :TimeZone => "Tokyo Standard Time"
          },
          :Attendees => [
              {
                :EmailAddress => {
                  :Address => "hoge@sample.com",
                  :Name => "Hoge"
                },
                :Type => "Required"
              },
              {
                :EmailAddress => {
                  :Address => "fuga@sample.com",
                  :Name => "Fuga"
                },
                :Type => "Required"
              }
          ]
        }.to_json
      end
      puts response.body
      redirect_to calendar_url
    else
      redirect_to root_url
    end
  end
end

create というメソッドを作成してこれでカレンダーを登録する POST リクエストを受け取ります
ポイントは req.body に Json を設定するところです
APIリファレンス にサンプルが記載されているのでそれを元に設定するといいと思います

簡単に各要素を説明すると

  • Subject・・・予定のタイトルを設定
  • Body・・・予定の本文を設定
  • Start・・・予定の開始時刻を設定
  • End・・・予定の終了時刻を設定
  • Attendees・・・予定の参加者を設定

という感じです
簡単なフォーマットチェックやバリデーションチェックは API 側でやってくれているようで Start が End を超えていたり時刻のフォーマットがおかしいと 400 系のエラーが返ってきます
response の body にエラーのメッセージも返っているので、それを見てもいいと思います

  • vim app/views/calendar/index.html.erb
<h1>My events</h1>
<table>
  <tr>
    <th>ID</th>
    <th>Subject</th>
    <th>Start</th>
    <th>End</th>
  </tr>
  <% @events.each do |event| %>
    <tr>
      <td><%= event['Id'] %></td>
      <td><%= event['Subject'] %></td>
      <td><%= event['Start'] %></td>
      <td><%= event['End'] %></td>
    </tr>
  <% end %>
</table>
<br>
<%= form_tag("/calendar", method: "post") do %>
  <%= label_tag(:subject, "予定名") %>
  <%= text_field_tag(:subject, '打ち合わせ') %>
  <%= label_tag(:start, "開始時刻") %>
  <%= text_field_tag(:start, Time.now.strftime("%Y-%m-%dT%H:%M:00")) %>
  <%= label_tag(:end, "終了時刻") %>
  <%= text_field_tag(:end, (Time.now + 3600).strftime("%Y-%m-%dT%H:%M:00")) %>
  <%= submit_tag("登録") %>
<% end %>

動作確認

  • cd o365-tutorial
  • rails server –bind=0.0.0.0

http://localhost:3000/ にブラウザでアクセスします
ログインリンクがあるのでログインすると最新のカレンダーに登録された予定の一覧が表示されるはずです
そして、画面したに予定を登録する登録フォームがあるので「登録」をクリックすれば予定が登録されるはずです

最後に

Office365 の カレンダー API を使って予定の登録をしてみました
日付は UTC で登録できるので localization してください

次は削除の API でも実行してみようと思います

参考サイト

0 件のコメント:

コメントを投稿