diff --git a/drivers/place/event_scrape.cr b/drivers/place/event_scrape.cr new file mode 100644 index 00000000000..957174518bf --- /dev/null +++ b/drivers/place/event_scrape.cr @@ -0,0 +1,84 @@ +require "place_calendar" + +class Place::EventScrape < PlaceOS::Driver + descriptive_name "PlaceOS Event Scrape" + generic_name :EventScrape + + default_settings({ + zone_ids: ["placeos-zone-id"], + internal_domains: ["PlaceOS.com"] + }) + + accessor staff_api : StaffAPI_1 + + @zone_ids = [] of String + @internal_domains = [] of String + + alias Event = PlaceCalendar::Event + + struct SystemWithEvents + include JSON::Serializable + + def initialize(@name : String, @zones : Array(String), @events : Array(Event)) + end + end + + def on_load + on_update + end + + def on_update + @zone_ids = setting?(Array(String), :zone_ids) || [] of String + @internal_domains = setting?(Array(String), :internal_domains) || [] of String + end + + def todays_bookings + response = { + internal_domains: @internal_domains, + systems: {} of String => SystemWithEvents + } + + now = Time.local + start_epoch = now.at_beginning_of_day.to_unix + end_epoch = now.at_end_of_day.to_unix + + @zone_ids.each do |z_id| + staff_api.systems(zone_id: z_id).get.as_a.each do |sys| + sys_id = sys["id"].as_s + # In case the same system is in multiple zones + next if response[:systems][sys_id]? + + response[:systems][sys_id] = SystemWithEvents.new( + name: sys["name"].as_s, + zones: Array(String).from_json(sys["zones"].to_json), + events: get_system_bookings(sys_id, start_epoch, end_epoch) + ) + end + end + + response + end + + def get_system_bookings(sys_id : String, start_epoch : Int64?, end_epoch : Int64?) : Array(Event) + booking_module = staff_api.modules_from_system(sys_id).get.as_a.find { |mod| mod["name"] == "Bookings" } + # If the system has a booking module with bookings + if booking_module && (bookings = staff_api.get_module_state(booking_module["id"].as_s).get["bookings"]?) + bookings = Array(Event).from_json(bookings.as_s) + + # If both start_epoch and end_epoch are passed + if start_epoch && end_epoch + # Convert start/end_epoch to Time object as Event.event_start.class == Time + start_time = Time.unix(start_epoch) + end_time = Time.unix(end_epoch) + range = (start_time..end_time) + # Only select bookings within start_epoch and end_epoch + bookings.select! { |b| range.includes?(b.event_start) } + bookings + end + + bookings + else + [] of Event + end + end +end diff --git a/drivers/place/event_scrape_spec.cr b/drivers/place/event_scrape_spec.cr new file mode 100644 index 00000000000..c59793aeb34 --- /dev/null +++ b/drivers/place/event_scrape_spec.cr @@ -0,0 +1,68 @@ +DriverSpecs.mock_driver "Place::EventScrape" do + system({ + StaffAPI: {StaffAPI} + }) + + exec(:get_bookings) +end + +class StaffAPI < DriverSpecs::MockDriver + def systems(zone_id : String) + logger.info { "requesting zone #{zone_id}" } + + sys_1 = { + id: "sys-1", + name: "Room 1", + zones: ["placeos-zone-id"] + } + + if zone_id == "placeos-zone-id" + [sys_1] + else + [ + sys_1, + { + id: "sys-2", + name: "Room 2", + zones: ["zone-1"] + } + ] + end + end + + def modules_from_system(system_id : String) + [ + { + id: "mod-1", + control_system_id: system_id, + name: "Calendar" + }, + { + id: "mod-2", + control_system_id: system_id, + name: "Bookings" + }, + { + id: "mod-3", + control_system_id: system_id, + name: "Bookings" + } + ] + end + + def get_module_state(module_id : String, lookup : String? = nil) + now = Time.local + start = now.at_beginning_of_day.to_unix + ending = now.at_end_of_day.to_unix + + { + bookings: [{ + event_start: start, + event_end: ending, + id: "booking-1", + host: "testroom1@booking.demo.acaengine.com", + title: "Test in #{module_id}" + }].to_json + } + end +end diff --git a/drivers/place/staff_api.cr b/drivers/place/staff_api.cr index edaa51efb6b..8206b940866 100644 --- a/drivers/place/staff_api.cr +++ b/drivers/place/staff_api.cr @@ -59,6 +59,26 @@ class Place::StaffAPI < PlaceOS::Driver end end + def systems(q : String? = nil, + limit : Int32 = 1000, + offset : Int32 = 0, + zone_id : String? = nil, + module_id : String? = nil, + features : String? = nil, + capacity : Int32? = nil, + bookable : Bool? = nil) + placeos_client.systems.search( + q: q, + limit: limit, + offset: offset, + zone_id: zone_id, + module_id: module_id, + features: features, + capacity: capacity, + bookable: bookable + ) + end + # Staff details returns the information from AD def staff_details(email : String) response = get("/api/staff/v1/people/#{email}", headers: { @@ -163,6 +183,64 @@ class Place::StaffAPI < PlaceOS::Driver ) end + # =================================== + # MODULE INFORMATION + # =================================== + def module(module_id : String) + response = get("/api/engine/v2/modules/#{module_id}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response for module id #{module_id}: #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue parsing module #{module_id}:\n#{response.body.inspect}" } + raise error + end + end + + def modules_from_system(system_id : String) + response = get("/api/engine/v2/modules?control_system_id=#{system_id}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response for modules for #{system_id}: #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue getting modules for #{system_id}:\n#{response.body.inspect}" } + raise error + end + end + + def get_module_state(module_id : String, lookup : String? = nil) + placeos_client.modules.state(module_id, lookup) + end + + # TODO: figure out why these 2 methods don't work + # def module(module_id : String) + # placeos_client.modules.fetch module_id + # end + + # def modules(q : String? = nil, + # limit : Int32 = 20, + # offset : Int32 = 0, + # control_system_id : String? = nil, + # driver_id : String? = nil) + # placeos_client.modules.search( + # q: q, + # limit: limit, + # offset: offset, + # control_system_id: control_system_id, + # driver_id: driver_id + # ) + # end + # =================================== # BOOKINGS ACTIONS # ===================================