diff --git a/.rubocop.yml b/.rubocop.yml index 8dc4d39..c296f1a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -20,13 +20,14 @@ RSpec/MultipleMemoizedHelpers: Max: 10 Metrics/MethodLength: - Max: 12 + Enabled: false Metrics/ParameterLists: - Max: 8 + Enabled: false Metrics/CyclomaticComplexity: Max: 9 Metrics/PerceivedComplexity: Max: 9 + diff --git a/lib/ms_graph_rest.rb b/lib/ms_graph_rest.rb index 4e13040..0c3548f 100644 --- a/lib/ms_graph_rest.rb +++ b/lib/ms_graph_rest.rb @@ -3,17 +3,29 @@ require 'multi_json' require_relative 'ms_graph_rest/version' -require_relative 'ms_graph_rest/mails' -require_relative 'ms_graph_rest/error' -require_relative 'ms_graph_rest/users' -require_relative 'ms_graph_rest/subscriptions' +require_relative 'ms_graph_rest/calendar_create_event' +require_relative 'ms_graph_rest/calendar_update_event' +require_relative 'ms_graph_rest/calendar_cancel_event' +require_relative 'ms_graph_rest/calendar_get_schedule' +require_relative 'ms_graph_rest/calendar_groups' require_relative 'ms_graph_rest/calendar_view' +require_relative 'ms_graph_rest/calendars' +require_relative 'ms_graph_rest/calendar' +require_relative 'ms_graph_rest/find_event' +require_relative 'ms_graph_rest/online_meetings' + +require_relative 'ms_graph_rest/error' +require_relative 'ms_graph_rest/find_rooms' +require_relative 'ms_graph_rest/groups' +require_relative 'ms_graph_rest/mails' require_relative 'ms_graph_rest/messages' require_relative 'ms_graph_rest/photos' -require_relative 'ms_graph_rest/groups' +require_relative 'ms_graph_rest/places' require_relative 'ms_graph_rest/planner_tasks' -require_relative 'ms_graph_rest/todo_lists' +require_relative 'ms_graph_rest/subscriptions' require_relative 'ms_graph_rest/todo_list_tasks' +require_relative 'ms_graph_rest/todo_lists' +require_relative 'ms_graph_rest/users' class Faraday::FileReadAdapter < Faraday::Adapter def self.folder=(val) @@ -70,23 +82,24 @@ def self.fake_folder=(val) end class BaseConnection - attr_reader :access_token + attr_reader :access_token, :version - def initialize(access_token:) + def initialize(access_token:, version: 'v1.0') @access_token = access_token.to_str.clone.freeze + @version = version end end class FaradayConnection < BaseConnection attr_reader :faraday_adapter - def initialize(access_token:, faraday_adapter:) - super(access_token: access_token) + def initialize(access_token:, faraday_adapter:, version: 'v1.0') + super(access_token: access_token, version: version) @faraday_adapter = faraday_adapter end def conn - @conn ||= Faraday.new(url: 'https://graph.microsoft.com/v1.0/', + @conn ||= Faraday.new(url: "https://graph.microsoft.com/#{@version}/", headers: { 'Content-Type' => 'application/json' }) do |c| c.use Faraday::Response::RaiseError c.authorization :Bearer, access_token @@ -96,24 +109,28 @@ def conn end end - def get_raw(path, params) - conn.get(path, params) + def get_raw(path, params, headers = {}) + conn.get(path, params, headers) rescue Faraday::Error => e raise MsGraphRest.wrap_request_error(e) end - def get(path, params) - response = get_raw(path, params) + # @param consistencylevel [String] "eventual" + def get(path, params, consistencylevel: nil, headers: {}) + if consistencylevel + headers["consistencylevel"] = consistencylevel + end + response = get_raw(path, params, headers) parse_response(response) end - def post(path, body) - response = conn.post(path, body.to_json) + def post(path, body, headers: {}) + response = conn.post(path, body.to_json, headers) parse_response(response) end - def patch(path, body) - response = conn.patch(path, body.to_json) + def patch(path, body, headers: {}) + response = conn.patch(path, body.to_json, headers) parse_response(response) end @@ -124,7 +141,12 @@ def delete(path) private def parse_response(response) - MultiJson.load(response.body) + body = response.body + if body.empty? + true + else + MultiJson.load(response.body) + end rescue MultiJson::ParseError => e raise MsGraphRest::ParseError.new(e.message, response.body) end @@ -133,10 +155,12 @@ def parse_response(response) class Client attr_reader :connection - def initialize(access_token:, faraday_adapter: Faraday.default_adapter) - @connection = FaradayConnection.new(access_token: access_token, faraday_adapter: faraday_adapter) + def initialize(access_token:, faraday_adapter: Faraday.default_adapter, version: 'v1.0') + @connection = FaradayConnection.new(access_token: access_token, faraday_adapter: faraday_adapter, + version: version) end + # @return Users def users Users.new(client: connection) end @@ -145,16 +169,67 @@ def subscriptions Subscriptions.new(client: connection) end + # @return MsGraphRest::Mails def mails Mails.new(client: connection) end + # @return MsGraphRest::Photos def photos Photos.new(client: connection) end - def calendar_view(path = '/me/calendar/') - CalendarView.new(path, client: connection) + def find_event + FindEvent.new(client: connection) + end + + # @return MsGraphRest::CalendarView + def calendar_view + CalendarView.new(client: connection) + end + + # @return MsGraphRest::CalendarGetSchedule + def calendar_get_schedule + CalendarGetSchedule.new(client: connection) + end + + # @return MsGraphRest::OnlineMeetings + def online_meetings + OnlineMeetings.new(client: connection) + end + + # @return MsGraphRest::CalendarCreateEvent + def calendar_create_event + CalendarCreateEvent.new(client: connection) + end + + # @return MsGraphRest::CalendarUpdateEvent + def calendar_update_event + CalendarUpdateEvent.new(client: connection) + end + + # @return MsGraphRest::CalendarCancelEvent + def calendar_cancel_event + CalendarCancelEvent.new(client: connection) + end + + # @return MsGraphRest::CalendarCreateEvent + def calendar_groups + CalendarGroups.new(client: connection) + end + + # @return MsGraphRest::CalendarCreateEvent + def calendars + Calendars.new(client: connection) + end + + def calendar + Calendar.new(client: connection) + end + + # @return MsGraphRest::Places + def places + Places.new(client: connection) end def messages(path = 'me') diff --git a/lib/ms_graph_rest/calendar.rb b/lib/ms_graph_rest/calendar.rb new file mode 100644 index 0000000..ddeb5d6 --- /dev/null +++ b/lib/ms_graph_rest/calendar.rb @@ -0,0 +1,31 @@ +require 'camel_snake_struct' + +module MsGraphRest + class Calendar < ChainableAction + class Response < ResponseWithPagination + end + Response.example('value' => nil, "@odata.context" => "", "@odata.nextLink" => "") + + attr_reader :client, :path, :query + + # rubocop:disable Lint/MissingSuper + def initialize(client:, query: {}) + @client = client + @query = query + end + # rubocop:enable Lint/MissingSuper + + def get(calendar_id:, user_id: nil, calendar_group: nil) + path = + case [user_id.nil?, calendar_group.nil?] + when [true, true] then "me/calendars/#{calendar_id}" + when [false, true] then "users/#{user_id}/calendars/#{calendar_id}" + when [true, false] then "me/calendarGroups/#{calendar_group}/calendars/#{calendar_id}" + when [false, false] then "users/#{user_id}/calendarGroups/#{calendar_group}/calendars/#{calendar_id}" + end + + Response.new(client.get(path, {})) + end + end +end + diff --git a/lib/ms_graph_rest/calendar_cancel_event.rb b/lib/ms_graph_rest/calendar_cancel_event.rb new file mode 100644 index 0000000..e8cf274 --- /dev/null +++ b/lib/ms_graph_rest/calendar_cancel_event.rb @@ -0,0 +1,41 @@ +module MsGraphRest + class CalendarCancelEvent < ModifyingAction + Response.example("value" => [], "@odata.context" => "", "@odata.nextLink" => "") + + # Issues getSchedule. If user_id is given, uses the + # /users/ID/calendar/getSchedule, otherwise me/calendar/getSchedule endpoint + # @return Response + # @param id [String] MSOffice Event ID + # @param user_id [String] Optional user id that is used for the request + # @param comment [String] Optional comment + # @param calendar_id [String] Optional calendar id + def cancel(id:, user_id: nil, comment: nil, calendar_id: nil) + # POST /me/events/{id}/cancel + # POST /users/{id | userPrincipalName}/events/{id}/cancel + # POST /groups/{id}/events/{id}/cancel + # + # POST /me/calendar/events/{id}/cancel + # POST /users/{id | userPrincipalName}/calendar/events/{id}/cancel + # POST /groups/{id}/calendar/events/{id}/cancel + # + # POST /me/calendars/{id}/events/{id}/cancel + # POST /users/{id | userPrincipalName}/calendars/{id}/events/{id}/cancel + # + # POST /me/calendarGroups/{id}/calendars/{id}/events/{id}/cancel + # POST /users/{id | userPrincipalName}/calendarGroups/{id}/calendars/{id}/events/{id}/cancel + path = case [user_id.present?, user_id.present?] + when [false, true] + "me/calendars/#{calendar_id}/events/#{id}/cancel" + when [true, false] + "users/#{user_id}/events/#{id}/cancel" + when [true, true] + "users/#{user_id}/calendars/#{calendar_id}/events/#{id}/cancel" + else + "me/events/#{id}/cancel" + end + body = { comment: comment }.compact + + client.post(path, body) + end + end +end diff --git a/lib/ms_graph_rest/calendar_create_event.rb b/lib/ms_graph_rest/calendar_create_event.rb new file mode 100644 index 0000000..d573cd8 --- /dev/null +++ b/lib/ms_graph_rest/calendar_create_event.rb @@ -0,0 +1,83 @@ +require 'camel_snake_struct' +require_relative 'modifying_action' +require_relative 'response' + +module MsGraphRest + class CalendarCreateEvent < ModifyingAction + Response.example("value" => [], "@odata.context" => "", "@odata.nextLink" => "") + + # Issues getSchedule. If user_id is given, uses the + # /users/ID/calendar/getSchedule, otherwise me/calendar/getSchedule endpoint + # @return Response + # @param start_time [Time] From Date Time + # @param end_time [Time] To Date Time + # @param subject [String] + # @param body [String] + # @param content_type [String] HTML or TEXT + # @param user_id [String] Optional user id that is used for the request + # @param importance [String] normal low high + # @param all_day [Boolean] + # @param draft [Boolean] + # @param allow_new_time_proposals [Boolean] + # @param attendees [Array] + # @param show_as [String] busy, tentative + # @param location [Hash] + def create( + subject:, + body:, + start_time:, + end_time:, + location:, + attendees:, + allow_new_time_proposals:, + user_id: nil, + calendar_id: nil, + all_day: false, + draft: false, + show_as: 'busy', + sensitivity: 'normal', + importance: 'normal', + content_type: "HTML" + ) + start_time = start_time.iso8601 if start_time.respond_to?(:iso8601) + end_time = end_time.iso8601 if end_time.respond_to?(:iso8601) + + body = { + subject: subject, + body: { + content: body, + contentType: content_type, + }, + sensitivity: sensitivity, + importance: importance, + start: { + dateTime: start_time, + timeZone: 'UTC' + }, + location: location, + end: { + dateTime: end_time, + timeZone: 'UTC' + }, + isAllDay: all_day, + showAs: show_as, + isDraft: draft, + allowNewTimeProposals: allow_new_time_proposals, + attendees: attendees, + }.compact + + path = case [user_id.present?, calendar_id.present?] + when [true, true] + "users/#{user_id}/calendars/#{calendar_id}/events" + when [false, true] + "me/calendars/#{calendar_id}/events" + when [true, false] + "users/#{user_id}/events" + else + "me/events" + end + + Response.new(client.post(path, body)) + end + end +end diff --git a/lib/ms_graph_rest/calendar_get_schedule.rb b/lib/ms_graph_rest/calendar_get_schedule.rb new file mode 100644 index 0000000..8ee14c8 --- /dev/null +++ b/lib/ms_graph_rest/calendar_get_schedule.rb @@ -0,0 +1,41 @@ +require 'camel_snake_struct' +require_relative 'chainable_action' +require_relative 'response_with_pagination' + +module MsGraphRest + class CalendarGetSchedule < ChainableAction + class Response < ResponseWithPagination + end + Response.example("value" => [], "@odata.context" => "", "@odata.nextLink" => "") + + # Issues getSchedule. If user_id is given, uses the + # /users/ID/calendar/getSchedule, otherwise me/calendar/getSchedule endpoint + # @return Response + # @param start_time [Time] Min Date Time + # @param end_time [Time] Max Date Time + # @param schedules [Array] List of user mails to get schedules for + # @param availability_view_interval [Integer] + # @param user_id [String] Optional user id that is used for the request + def get(start_time:, end_time:, schedules:, availability_view_interval: nil, user_id: nil) + start_time = start_time.iso8601 if start_time.respond_to?(:iso8601) + end_time = end_time.iso8601 if end_time.respond_to?(:iso8601) + + path = user_id ? "users/#{CGI.escape(user_id)}/calendar/getSchedule" : "me/calendar/getSchedule" + + body = { + startTime: { + dateTime: start_time, + timeZone: 'UTC' + }, + endTime: { + dateTime: end_time, + timeZone: 'UTC' + }, + schedules: schedules, + availabilityViewInterval: availability_view_interval + }.compact + + Response.new(client.post(path + "?#{query.to_query}", body)) + end + end +end diff --git a/lib/ms_graph_rest/calendar_groups.rb b/lib/ms_graph_rest/calendar_groups.rb new file mode 100644 index 0000000..d8ac3c6 --- /dev/null +++ b/lib/ms_graph_rest/calendar_groups.rb @@ -0,0 +1,23 @@ +require 'camel_snake_struct' + +module MsGraphRest + class CalendarGroups < ChainableAction + class Response < ResponseWithPagination + end + Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") + + attr_reader :client, :path, :query + + # rubocop:disable Lint/MissingSuper + def initialize(client:, query: {}) + @client = client + @query = query + end + # rubocop:enable Lint/MissingSuper + + def get(user_id:) + path = user_id ? "users/#{user_id}/calendarGroups" : "me/calendarGroups" + Response.new(client.get(path, {})) + end + end +end diff --git a/lib/ms_graph_rest/calendar_update_event.rb b/lib/ms_graph_rest/calendar_update_event.rb new file mode 100644 index 0000000..242da48 --- /dev/null +++ b/lib/ms_graph_rest/calendar_update_event.rb @@ -0,0 +1,98 @@ +require 'camel_snake_struct' +require_relative 'modifying_action' +require_relative 'response' + +module MsGraphRest + class CalendarUpdateEvent < ModifyingAction + Response.example("value" => [], "@odata.context" => "", "@odata.nextLink" => "") + + # Issues getSchedule. If user_id is given, uses the + # /users/ID/calendar/getSchedule, otherwise me/calendar/getSchedule endpoint + # @return Response + # @param id [String] MSOffice Event ID + # @param start_time [Time] From Date Time + # @param end_time [Time] To Date Time + # @param subject [String] + # @param body [String] + # @param content_type [String] HTML or TEXT + # @param user_id [String] Optional user id that is used for the request + # @param importance [String] normal low high + # @param all_day [Boolean] + # @param draft [Boolean] + # @param allow_new_time_proposals [Boolean] + # @param attendees [Array] + # @param location [Hash] + def create( + id:, + subject:, + body:, + start_time:, + end_time:, + location:, + attendees:, + allow_new_time_proposals:, + user_id: nil, + calendar_id: nil, + show_as: 'busy', + all_day: false, + draft: false, + sensitivity: 'normal', + importance: 'normal', + content_type: "HTML" + ) + start_time = start_time.iso8601 if start_time.respond_to?(:iso8601) + end_time = end_time.iso8601 if end_time.respond_to?(:iso8601) + + body = { + subject: subject, + body: { + content: body, + contentType: content_type, + }, + sensitivity: sensitivity, + importance: importance, + start: { + dateTime: start_time, + timeZone: 'UTC' + }, + location: location, + end: { + dateTime: end_time, + timeZone: 'UTC' + }, + isAllDay: all_day, + showAs: show_as, + isDraft: draft, + allowNewTimeProposals: allow_new_time_proposals, + attendees: attendees, + }.compact + + # PATCH /me/events/{id} + # PATCH /users/{id | userPrincipalName}/events/{id} + # PATCH /groups/{id}/events/{id} + # + # PATCH /me/calendar/events/{id} + # PATCH /users/{id | userPrincipalName}/calendar/events/{id} + # PATCH /groups/{id}/calendar/events/{id} + # + # PATCH /me/calendars/{id}/events/{id} + # PATCH /users/{id | userPrincipalName}/calendars/{id}/events/{id} + # + # PATCH /me/calendarGroups/{id}/calendars/{id}/events/{id} + # PATCH /users/{id | userPrincipalName}/calendarGroups/{id}/calendars/{id}/events/{id} + path = case [user_id.present?, calendar_id.present?] + when [true, true] + "users/#{user_id}/calendars/#{calendar_id}/events/#{id}" + when [false, true] + "me/calendars/#{calendar_id}/events/#{id}" + when [true, false] + "users/#{user_id}/events/#{id}" + else + "me/events/#{id}" + end + + + Response.new(client.patch(path, body)) + end + end +end diff --git a/lib/ms_graph_rest/calendar_view.rb b/lib/ms_graph_rest/calendar_view.rb index d237f3c..7ec08dc 100644 --- a/lib/ms_graph_rest/calendar_view.rb +++ b/lib/ms_graph_rest/calendar_view.rb @@ -1,75 +1,29 @@ require 'camel_snake_struct' module MsGraphRest - class CalendarView - class Response < CamelSnakeStruct - include Enumerable - - def initialize(data) - @data = data - super(data) - end - - def each - value.each { |val| yield(val) } - end - - def next_get_query - return nil unless odata_next_link - - uri = URI.parse(odata_next_link) - params = CGI.parse(uri.query) - { start_date_time: params["startDateTime"]&.first, - end_date_time: params["endDateTime"]&.first, - skip: params["$skip"]&.first, - top: params["$top"]&.first, - select: params["$select"]&.first }.compact - end - - def size - value.size - end - - def to_h - to_hash - end + class CalendarView < ChainableAction + class Response < ResponseWithPagination end Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") attr_reader :client, :path, :query - def initialize(path, client:, query: {}) - @path = "#{path.to_str}".gsub('//', '/') - @path[0] = '' if @path.start_with?('/') + # rubocop:disable Lint/MissingSuper + def initialize(client:, query: {}) @client = client @query = query end + # rubocop:enable Lint/MissingSuper - def get(start_date_time:, end_date_time:, skip: nil, top: nil, select: nil) + def get(start_date_time:, end_date_time:, user_id: nil) start_date_time = start_date_time.iso8601 if start_date_time.respond_to?(:iso8601) end_date_time = end_date_time.iso8601 if end_date_time.respond_to?(:iso8601) + path = user_id ? "users/#{user_id}/calendar/calendarView" : "me/calendar/calendarView" - Response.new(client.get("#{path}/calendarView", - query.merge({ 'startDateTime' => start_date_time, - 'endDateTime' => end_date_time, - '$skip' => skip, - '$top' => top, - '$select' => select }.compact))) - end - - def create(options) - Response.new(client.post("#{path}", options)) - end - - def select(val) - val = val.map(&:to_s).map { |v| v.camelize(:lower) }.join(',') if val.is_a?(Array) - new_with_query(query.merge('$select' => val)) - end - - private + query['startDateTime'] = start_date_time + query['endDateTime'] = end_date_time - def new_with_query(query) - self.class.new(path, client: client, query: query) + Response.new(client.get(path, query)) end end end diff --git a/lib/ms_graph_rest/calendars.rb b/lib/ms_graph_rest/calendars.rb new file mode 100644 index 0000000..88f4624 --- /dev/null +++ b/lib/ms_graph_rest/calendars.rb @@ -0,0 +1,30 @@ +require 'camel_snake_struct' + +module MsGraphRest + class Calendars < ChainableAction + class Response < ResponseWithPagination + end + Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") + + attr_reader :client, :path, :query + + # rubocop:disable Lint/MissingSuper + def initialize(client:, query: {}) + @client = client + @query = query + end + # rubocop:enable Lint/MissingSuper + + def get(user_id: nil, calendar_group: nil) + path = + case [user_id.nil?, calendar_group.nil?] + when [true, true] then "me/calendars" + when [false, true] then "users/#{user_id}/calendars" + when [true, false] then "me/calendarGroups/#{calendar_group}/calendars" + when [false, false] then "users/#{user_id}/calendarGroups/#{calendar_group}/calendars" + end + + Response.new(client.get(path, {})) + end + end +end diff --git a/lib/ms_graph_rest/chainable_action.rb b/lib/ms_graph_rest/chainable_action.rb new file mode 100644 index 0000000..beb641c --- /dev/null +++ b/lib/ms_graph_rest/chainable_action.rb @@ -0,0 +1,76 @@ +require "active_support/core_ext/hash/indifferent_access" + +module MsGraphRest + class ChainableAction + attr_reader :client, :query + + def initialize(client:, query: {}) + @client = client + @query = query.with_indifferent_access + end + + def get() + raise NotImplementedError + end + + # Auto paginates until no odata_next_link is left any more. Use with caution + # Uses .get underhood + # @param **args + def all(**args) + out = nil + each_page(**args) do |response| + if out + out = out.merge_with_next_page(response) + else + out = response + end + end + out + end + + # Auto paginates until no odata_next_link is left any more. Use with caution + # Uses .get underhood + # @param **args + # @yield ResponseWithPagination + def each_page(**args) + loop do + response = get(**args) + yield(response) + + break if response.odata_next_link.nil? + + parameters = Rack::Utils.parse_nested_query(URI(response.odata_next_link).query) + parameters.each do |key, value| + query[key] = value + end + end + end + + def select(val) + val = val.map(&:to_s).map { |v| v.camelize(:lower) }.join(',') if val.is_a?(Array) + new_with_query(query.merge("$select": val)) + end + + def top(val) + new_with_query(query.merge("$top": val)) + end + + def skip(val) + new_with_query(query.merge("$skip": val)) + end + + def skiptoken(val) + new_with_query(query.merge("$skiptoken": val)) + end + + def filter(val) + new_with_query(query.merge("$filter": val)) + end + + private + + def new_with_query(query) + self.class.new(client: client, query: query) + end + end +end diff --git a/lib/ms_graph_rest/find_event.rb b/lib/ms_graph_rest/find_event.rb new file mode 100644 index 0000000..ce9a1bc --- /dev/null +++ b/lib/ms_graph_rest/find_event.rb @@ -0,0 +1,10 @@ +module MsGraphRest + class FindEvent < ChainableAction + Response.example("value" => [], "@odata.context" => "", "@odata.nextLink" => "") + + def get(id:, user_id: nil) + path = user_id ? "users/#{CGI.escape(user_id)}/events/#{id}" : "me/events/#{id}" + Response.new(client.get(path, {})) + end + end +end diff --git a/lib/ms_graph_rest/find_rooms.rb b/lib/ms_graph_rest/find_rooms.rb new file mode 100644 index 0000000..1ebfc02 --- /dev/null +++ b/lib/ms_graph_rest/find_rooms.rb @@ -0,0 +1,20 @@ +require_relative 'chainable_action' +require_relative 'response_with_pagination' + +module MsGraphRest + class FindRooms < ChainableAction + class Response < ResponseWithPagination + end + Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") + + # @param tenant_id [String] Optional tenant_id, if not given, uses the /me path + # @param room_list [String] E-Mail address of the room list to filter on + def get(tenant_id: nil, room_list: nil) + path = tenant_id ? "users/#{CGI.escape(tenant_id)}/findRooms" : "me/findRooms" + if room_list + path += "(RoomList='#{CGI.escape(room_list)}')" + end + Response.new(client.get(path, query)) + end + end +end diff --git a/lib/ms_graph_rest/modifying_action.rb b/lib/ms_graph_rest/modifying_action.rb new file mode 100644 index 0000000..becce02 --- /dev/null +++ b/lib/ms_graph_rest/modifying_action.rb @@ -0,0 +1,13 @@ +module MsGraphRest + class ModifyingAction + attr_reader :client, :query + + def initialize(client:) + @client = client + end + + def create() + raise NotImplementedError + end + end +end diff --git a/lib/ms_graph_rest/online_meetings.rb b/lib/ms_graph_rest/online_meetings.rb new file mode 100644 index 0000000..097c5b2 --- /dev/null +++ b/lib/ms_graph_rest/online_meetings.rb @@ -0,0 +1,55 @@ +require_relative 'chainable_action' +require_relative 'response_with_pagination' + +module MsGraphRest + class OnlineMeetings < ChainableAction + class Response < ResponseWithPagination + end + Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") + + def create(subject:, start_date_time:, end_date_time:, accept_language: "en", participants: nil, user_id: nil, **args) + start_date_time = start_date_time.iso8601 if start_date_time.respond_to?(:iso8601) + end_date_time = end_date_time.iso8601 if end_date_time.respond_to?(:iso8601) + + body = { + startDateTime: start_date_time, + endDateTime: end_date_time, + subject: subject, + participants: participants + }.merge(args) + path = user_id ? "users/#{CGI.escape(user_id)}/onlineMeetings" : "me/onlineMeetings" + headers = { + "Accept-Language" => accept_language + } + Response.new(client.post(path, body, headers: headers)) + end + + # https://docs.microsoft.com/en-us/graph/api/onlinemeeting-update + def update( + id:, subject: nil, start_date_time: nil, end_date_time: nil, participants: nil, user_id: nil, accept_language: nil, **args + ) + start_date_time = start_date_time.iso8601 if start_date_time.respond_to?(:iso8601) + end_date_time = end_date_time.iso8601 if end_date_time.respond_to?(:iso8601) + + body = { + startDateTime: start_date_time, + endDateTime: end_date_time, + subject: subject, + participants: participants + }.merge(args) + path = user_id ? "users/#{user_id}/onlineMeetings/#{id}" : "me/onlineMeetings/#{id}" + headers = { + "Accept-Language" => accept_language + }.compact + Response.new(client.patch(path, body, headers: headers)) + end + + def delete(id:, user_id: nil) + # /me/onlineMeetings/{meetingId} + # DELETE /users/{userId}/onlineMeetings/{meetingId} + path = user_id ? "users/#{user_id}/onlineMeetings/#{id}" : "me/onlineMeetings/#{id}" + + client.delete(path) + end + end +end diff --git a/lib/ms_graph_rest/places.rb b/lib/ms_graph_rest/places.rb new file mode 100644 index 0000000..6db1a96 --- /dev/null +++ b/lib/ms_graph_rest/places.rb @@ -0,0 +1,17 @@ +require_relative 'chainable_action' +require_relative 'response_with_pagination' + +module MsGraphRest + class Places < ChainableAction + class Response < ResponseWithPagination + end + Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") + + # @param path [String] either microsoft.graph.room or microsoft.graph.roomlist + def get(path: 'microsoft.graph.room') + raise ArgumentError unless path.in?(['microsoft.graph.room', 'microsoft.graph.roomlist']) + + Response.new(client.get("places/#{path}", query)) + end + end +end diff --git a/lib/ms_graph_rest/response.rb b/lib/ms_graph_rest/response.rb new file mode 100644 index 0000000..a5a52c2 --- /dev/null +++ b/lib/ms_graph_rest/response.rb @@ -0,0 +1,24 @@ +require 'camel_snake_struct' + +module MsGraphRest + class Response < CamelSnakeStruct + include Enumerable + + def initialize(data) + @data = data + super(data) + end + + def each + value.each { |val| yield(val) } + end + + def size + value.size + end + + def to_h + to_hash + end + end +end diff --git a/lib/ms_graph_rest/response_with_pagination.rb b/lib/ms_graph_rest/response_with_pagination.rb new file mode 100644 index 0000000..f636945 --- /dev/null +++ b/lib/ms_graph_rest/response_with_pagination.rb @@ -0,0 +1,43 @@ +require 'camel_snake_struct' + +module MsGraphRest + class ResponseWithPagination < CamelSnakeStruct + include Enumerable + + def initialize(data) + @data = data + super(data) + end + + def each + value.each { |val| yield(val) } + end + + def next_get_query + return nil unless odata_next_link + + uri = URI.parse(odata_next_link) + params = CGI.parse(uri.query) + { + skip: params["$skip"]&.first, + skiptoken: params["$skiptoken"]&.first, + top: params["$top"]&.first, + select: params["$select"]&.first + }.compact + end + + def merge_with_next_page(response) + new_data = response.to_hash + new_data['value'] = to_hash['value'] + new_data['value'] + self.class.new(new_data) + end + + def size + value.size + end + + def to_h + to_hash + end + end +end diff --git a/lib/ms_graph_rest/users.rb b/lib/ms_graph_rest/users.rb index a0b4d70..b37f8f0 100644 --- a/lib/ms_graph_rest/users.rb +++ b/lib/ms_graph_rest/users.rb @@ -1,67 +1,18 @@ -require 'camel_snake_struct' +require_relative 'chainable_action' +require_relative 'response_with_pagination' module MsGraphRest - class Users - class Response < CamelSnakeStruct - include Enumerable - - def initialize(data) - @data = data - super(data) - end - - def each - value.each { |val| yield(val) } - end - - def next_get_query - return nil unless odata_next_link - - uri = URI.parse(odata_next_link) - params = CGI.parse(uri.query) - { select: params["$select"]&.first, - skiptoken: params["$skiptoken"]&.first, - filter: params["$filter"]&.first }.compact - end - - def size - value.size - end - - def [](key) - @data[key] - end + class Users < ChainableAction + class Response < ResponseWithPagination end Response.example('value' => [], "@odata.context" => "", "@odata.nextLink" => "") - attr_reader :client - attr_reader :query - - def initialize(client:, query: {}) - @client = client - @query = query + def get + Response.new(client.get("users", query)) end - def get(select: nil, filter: nil, skiptoken: nil) - Response.new(client.get("users", query.merge({ '$select' => select, - '$filter' => filter, - '$skiptoken' => skiptoken }.compact))) - end - - def filter(val) - new_with_query(query.merge('$filter' => val)) - end - - def select(val) - val = val.map(&:to_s).map { |v| v.camelize(:lower) }.join(',') if val.is_a?(Array) - new_with_query(query.merge('$select' => val)) - end - - private - - def new_with_query(query) - self.class.new(client: client, - query: query) + def count + client.get("users/$count", query, consistencylevel: "eventual") end end end diff --git a/ms_graph_rest.gemspec b/ms_graph_rest.gemspec index 8f30fa5..0f142ba 100644 --- a/ms_graph_rest.gemspec +++ b/ms_graph_rest.gemspec @@ -24,9 +24,9 @@ Gem::Specification.new do |spec| spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'activesupport', '>= 3.2', '< 7.0' - spec.add_dependency 'camel_snake_struct', '>= 0.1.0', '< 2.0' + spec.add_dependency 'activesupport', '>= 3.2', '< 8.0' + spec.add_dependency 'camel_snake_struct', '>= 0.1.0' spec.add_dependency 'faraday', '>= 0.10.0', '< 2.0' - spec.add_dependency 'hashie', '>= 3.1.0', '< 5.0' - spec.add_dependency 'multi_json', '>= 1.4.0', '< 2.0' + spec.add_dependency 'hashie', '>= 3.1.0' + spec.add_dependency 'multi_json', '>= 1.4.0' end