Class: Timely::Sources::Outlook
- Inherits:
-
Object
- Object
- Timely::Sources::Outlook
- Defined in:
- lib/timely/sources/outlook.rb
Constant Summary collapse
- GRAPH_BASE =
'https://graph.microsoft.com/v1.0'- AUTH_URL =
'https://login.microsoftonline.com/common/oauth2/v2.0'- SCOPES =
'Calendars.ReadWrite offline_access'
Instance Attribute Summary collapse
-
#last_error ⇒ Object
readonly
Returns the value of attribute last_error.
Instance Method Summary collapse
-
#create_event(event_data) ⇒ Object
Create event.
-
#delete_event(event_id) ⇒ Object
Delete event.
-
#fetch_events(time_min, time_max) ⇒ Object
Fetch events for a date range.
-
#initialize(config = {}) ⇒ Outlook
constructor
A new instance of Outlook.
-
#list_calendars ⇒ Object
List calendars.
-
#poll_for_token(device_code) ⇒ Object
Device code flow - Step 2: poll for token.
-
#refresh_access_token ⇒ Object
Refresh access token.
-
#respond_to_event(event_id, response) ⇒ Object
Accept/decline/tentative.
-
#start_device_auth ⇒ Object
Device code flow - Step 1: get device code Returns { user_code:, device_code:, verification_uri:, message: }.
-
#update_event(event_id, event_data) ⇒ Object
Update event.
Constructor Details
#initialize(config = {}) ⇒ Outlook
Returns a new instance of Outlook.
15 16 17 18 19 20 21 22 |
# File 'lib/timely/sources/outlook.rb', line 15 def initialize(config = {}) @client_id = config['client_id'] @tenant_id = config['tenant_id'] || 'common' @access_token = config['access_token'] @refresh_token = config['refresh_token'] @token_expires_at = 0 @last_error = nil end |
Instance Attribute Details
#last_error ⇒ Object (readonly)
Returns the value of attribute last_error.
13 14 15 |
# File 'lib/timely/sources/outlook.rb', line 13 def last_error @last_error end |
Instance Method Details
#create_event(event_data) ⇒ Object
Create event
127 128 129 130 131 |
# File 'lib/timely/sources/outlook.rb', line 127 def create_event(event_data) body = to_outlook_format(event_data) data = api_post('/me/events', body) data ? data['id'] : nil end |
#delete_event(event_id) ⇒ Object
Delete event
140 141 142 |
# File 'lib/timely/sources/outlook.rb', line 140 def delete_event(event_id) api_delete("/me/events/#{event_id}") end |
#fetch_events(time_min, time_max) ⇒ Object
Fetch events for a date range
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/timely/sources/outlook.rb', line 110 def fetch_events(time_min, time_max) start_str = Time.at(time_min).utc.strftime('%Y-%m-%dT%H:%M:%SZ') end_str = Time.at(time_max).utc.strftime('%Y-%m-%dT%H:%M:%SZ') events = [] url = "/me/calendarView?startDateTime=#{start_str}&endDateTime=#{end_str}&$top=250&$orderby=start/dateTime" while url data = api_get(url) break unless data && data['value'] data['value'].each { |item| events << normalize_event(item) } url = data['@odata.nextLink']&.sub(GRAPH_BASE, '') end events end |
#list_calendars ⇒ Object
List calendars
101 102 103 104 105 106 107 |
# File 'lib/timely/sources/outlook.rb', line 101 def list_calendars data = api_get('/me/calendars') return [] unless data && data['value'] data['value'].map do |cal| { id: cal['id'], name: cal['name'], color: cal['color'], can_edit: cal['canEdit'] } end end |
#poll_for_token(device_code) ⇒ Object
Device code flow - Step 2: poll for token
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/timely/sources/outlook.rb', line 44 def poll_for_token(device_code) uri = URI("#{AUTH_URL.sub('common', @tenant_id)}/token") loop do res = Net::HTTP.post_form(uri, { 'client_id' => @client_id, 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', 'device_code' => device_code }) data = JSON.parse(res.body) if data['access_token'] @access_token = data['access_token'] @refresh_token = data['refresh_token'] @token_expires_at = Time.now.to_i + (data['expires_in'] || 3600).to_i - 60 return { access_token: @access_token, refresh_token: @refresh_token } elsif data['error'] == 'authorization_pending' sleep 5 elsif data['error'] == 'slow_down' sleep 10 else @last_error = data['error_description'] || data['error'] return nil end end rescue => e @last_error = e. nil end |
#refresh_access_token ⇒ Object
Refresh access token
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/timely/sources/outlook.rb', line 73 def refresh_access_token return @access_token if @access_token && Time.now.to_i < @token_expires_at return nil unless @refresh_token && @client_id uri = URI("#{AUTH_URL.sub('common', @tenant_id)}/token") res = Net::HTTP.post_form(uri, { 'client_id' => @client_id, 'grant_type' => 'refresh_token', 'refresh_token' => @refresh_token, 'scope' => SCOPES }) if res.is_a?(Net::HTTPSuccess) data = JSON.parse(res.body) @access_token = data['access_token'] @refresh_token = data['refresh_token'] if data['refresh_token'] @token_expires_at = Time.now.to_i + (data['expires_in'] || 3600).to_i - 60 @access_token else @last_error = "Token refresh failed: #{res.code}" nil end rescue => e @last_error = e. nil end |
#respond_to_event(event_id, response) ⇒ Object
Accept/decline/tentative
145 146 147 148 149 150 151 152 153 |
# File 'lib/timely/sources/outlook.rb', line 145 def respond_to_event(event_id, response) endpoint = case response.to_s when 'accepted', 'accept' then 'accept' when 'declined', 'decline' then 'decline' when 'tentative', 'tentativelyAccept' then 'tentativelyAccept' else return false end api_post("/me/events/#{event_id}/#{endpoint}", { 'sendResponse' => true }) end |
#start_device_auth ⇒ Object
Device code flow - Step 1: get device code Returns { user_code:, device_code:, verification_uri:, message: }
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/timely/sources/outlook.rb', line 26 def start_device_auth uri = URI("#{AUTH_URL.sub('common', @tenant_id)}/devicecode") res = Net::HTTP.post_form(uri, { 'client_id' => @client_id, 'scope' => SCOPES }) if res.is_a?(Net::HTTPSuccess) JSON.parse(res.body) else @last_error = "Device auth failed: #{res.code}" nil end rescue => e @last_error = e. nil end |
#update_event(event_id, event_data) ⇒ Object
Update event
134 135 136 137 |
# File 'lib/timely/sources/outlook.rb', line 134 def update_event(event_id, event_data) body = to_outlook_format(event_data) api_patch("/me/events/#{event_id}", body) end |