Initial commit

This commit is contained in:
Senad Uka
2022-03-23 05:44:42 +01:00
parent 1405281a5c
commit eea10dd03b
113 changed files with 3617 additions and 81 deletions

View File

@@ -0,0 +1,38 @@
# frozen_string_literal: true
module Amqp
class AmqpService
include Singleton
def queue(queue_name, options = {})
@queue ||= {}
@queue[queue_name] ||= channel.queue queue_name, options
end
def exchange(exchange_name, exchange_type, options = {})
@exchange ||= {}
@exchange[exchange_name] ||= channel.exchange exchange_name, options.merge(type: exchange_type)
end
def default_exchange
channel.default_exchange
end
private
def channel
@channel ||= connection.create_channel
end
def connection
@connection ||= begin
Bunny.new(amqp_config).tap do |conn|
conn.start
end
end
end
def amqp_config
AppConfig.amqp
end
end
end

View File

@@ -0,0 +1,48 @@
# frozen_string_literal: true
require 'auth/token_service'
# Manages tokens for this App
module Auth
class AuthTokenService
include Singleton
def refresh
@tokens = mutex.synchronize { obtain_tokens }
end
def access_token
ensure_tokens
@tokens[:access_token]
end
private
def ensure_tokens
@tokens ||= mutex.synchronize { obtain_tokens }
end
def obtain_tokens
token_source.fetch('client_credentials', client_id, client_secret)
end
def mutex
@mutex ||= Mutex.new
end
def token_source
@token_source ||= Auth::TokenService.new(auth_url)
end
def client_id
AppConfig.auth.client_id
end
def client_secret
AppConfig.auth.client_secret
end
def auth_url
URI(AppConfig.auth.url)
end
end
end

111
app/services/log/log.rb Normal file
View File

@@ -0,0 +1,111 @@
# frozen_string_literal: true
module Log
class Log
class LogFormatter < Logger::Formatter
attr_reader :options
def initialize(options = {})
@options = options
super() # superclass does not take parameters
end
def call(severity, timestamp, progname, msg)
super(severity, timestamp, progname, "#{log_tags}#{msg}")
end
def log_tags
tags = formatted_tags.join ' '
tags.blank? ? '' : "[#{tags}] "
end
private
def formatted_tags
elems = [Thread.current[:name].to_s]
elems += (options[:tags] || []).map { |e| e.is_a?(Proc) ? e.call : e }
elems.reject(&:blank?)
end
end
attr_reader :options # default = {}
# Options may include:
# tags: an array of static elements and/or callable Procs to include in log output
# filename: specifies filename for log in Rails log directory
# stream: specifies stream for log output (included for test support)
def initialize(options = {})
@options = options
end
def logger
@logger ||= begin
new_logger = Logger.new log_destination
new_logger.level = numeric_log_level
new_logger.formatter = LogFormatter.new options
new_logger
end
end
def numeric_log_level
AppConfig.numeric_log_level
end
# Returns filename for log or stream object if specified in options.
def log_destination
options[:stream].present? ? options[:stream] : log_filepath
end
def log_filepath
@log_filepath ||= log_dir.join log_filename
end
def log_filename
options[:filename].present? ? options[:filename].to_s : "#{Rails.env.to_s}.log"
end
def log_dir
@log_dir ||= begin
dir = Rails.root.join 'log'
FileUtils.mkdir_p dir
dir
end
end
# Same semantics as Log.error except uses current instance of logger.
def error(msg, exception = nil, options = {})
Log.log_error logger, msg, exception, options
end
# Same semantics as Log.raise_error except uses current instance of logger.
def raise_error(msg, exception, options = {})
error msg, exception, options
raise exception
end
class << self
# Logs and re-raises exception.
def raise_error (msg, exception, options = {})
error msg, exception, options
raise exception
end
# Logs error with exception backtrace detail if available. Always uses Rails.logger.
#
# Options may include:
# no_backtrace: true suppresses backtrace
#
# Returns formatted msg.
def error(msg, exception = nil, options = {})
log_error Rails.logger, msg, exception, options
end
# Same semantics as Log.error except uses specified logger.
def log_error(logger, msg, exception = nil, options = {})
message = exception.nil? ? msg : "Exception #{exception.class.name}: #{exception.inspect} #{msg}"
logger.error message
logger.error exception.backtrace if exception.try(:backtrace) && !options[:no_backtrace]
message
end
end
end
end

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
require_relative 'logging'
module Log
module Loggable
def logger
Logging.instance.logger
end
end
end

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'singleton'
module Log
class Logging
include Singleton
attr_accessor :logger
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
require_relative '../log/loggable'
module SchedulePipeline
class Errors
include Log::Loggable
def push(error)
logger.error error
end
end
end

View File

@@ -0,0 +1,56 @@
# frozen_string_literal: true
require_relative '../log/loggable'
require_relative '../vendors/broad_sign/broad_sign_tokens'
require_relative '../vendors/broad_sign/broad_sign_fetch_schedule'
require_relative '../vendors/vistar/vistar_tokens'
require_relative '../vendors/vistar/vistar_fetch_schedule'
module SchedulePipeline
class FetchSchedule
include Log::Loggable
def initialize(in_queue, out_queue, errors)
@in_queue = in_queue
@out_queue = out_queue
@errors = errors
end
def start
@in_queue.subscribe { |msg| process_msg(msg) }
end
def process_msg(msg)
logger.debug("fetch schedule: #{msg}")
vendor = msg[:vendor]
vendor_fetch = for_vendor(vendor)
unless vendor_fetch
@errors.push("FetchSchedule: Unknown vendor #{vendor}")
return
end
params = msg[:params]
fetched = vendor_fetch.call(params)
unless fetched
@errors.push("FetchSchedule: No ads returned from vendor #{vendor}")
return
end
Models::ScheduleProcessMsg.new(vendor, params[:player], fetched).push(@out_queue) if fetched
true
rescue StandardError => e
@errors.push(e.message)
end
private
def for_vendor(vendor)
vendor_map[vendor&.to_sym]
end
def vendor_map
@vendor_map ||= {
broad_sign: Vendors::BroadSign::BroadSignFetchSchedule.new(Vendors::BroadSign::BroadSignTokens.instance),
vistar: Vendors::Vistar::VistarFetchSchedule.new(Vendors::Vistar::VistarTokens.instance)
}
end
end
end

View File

@@ -0,0 +1,37 @@
# frozen_string_literal: true
module SchedulePipeline
module Models
class Schedule
attr_reader :name, :vendor, :player, :start_time, :items
def initialize(name, vendor, player, start_time, items)
@name = name
@vendor = vendor
@player = player
@start_time = start_time
@items = items
end
def to_hash
{
name: name,
vendor: vendor,
player: player,
start_time: start_time,
items: items.collect(&:to_hash)
}.with_indifferent_access
end
def self.from_hash(input)
self.new(
input[:name],
input[:vendor],
input[:player],
input[:start_time],
input[:items].collect { |i| ScheduleItem.from_hash(i) }
)
end
end
end
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
module SchedulePipeline
module Models
class ScheduleFetchMsg
attr_reader :vendor, :params
def initialize(vendor, params)
@vendor = vendor
@params = params
end
def send(queue)
queue.push(serialized_msg)
end
private
def serialized_msg
{
vendor: @vendor,
params: @params
}
end
end
end
end

View File

@@ -0,0 +1,31 @@
# frozen_string_literal: true
module SchedulePipeline
module Models
class ScheduleItem
attr_reader :duration, :content_key, :pop_data
def initialize(duration, content_key, pop_data)
@duration = duration
@content_key = content_key
@pop_data = pop_data
end
def to_hash
{
duration: @duration,
content_key: @content_key,
pop_data: @pop_data
}.with_indifferent_access
end
def self.from_hash(input)
self.new(
input[:duration],
input[:content_key],
input[:pop_data]
)
end
end
end
end

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
module SchedulePipeline
module Models
class ScheduleProcessMsg
attr_reader :vendor, :player, :vendor_schedule
def initialize(vendor, player, schedule)
@vendor = vendor
@player = player
@vendor_schedule = schedule.with_indifferent_access
end
def push(queue)
queue.push(to_hash)
end
def to_hash
{
vendor: @vendor,
player: @player,
vendor_schedule: @vendor_schedule
}
end
def self.from_hash(input)
self.new(
input[:vendor],
input[:player],
input[:vendor_schedule],
)
end
end
end
end

View File

@@ -0,0 +1,85 @@
# frozen_string_literal: true
require_relative '../log/loggable'
require_relative '../vle/vle_settings'
require_relative '../vle/vle_create_asset'
require_relative '../vle/vle_ingest_asset'
require_relative '../vendors/broad_sign/broad_sign_transform_schedule'
require_relative '../vendors/vistar/vistar_transform_schedule'
require_relative '../auth/auth_token_service'
require 'auth/client/request/auth_aware_request'
module SchedulePipeline
class ProcessSchedule
include Log::Loggable
def initialize(in_queue, out_queue, errors, tokens = Auth::AuthTokenService.instance)
@in_queue = in_queue
@out_queue = out_queue
@errors = errors
@tokens = tokens
end
def start
@in_queue.subscribe do |msg|
msg = Models::ScheduleProcessMsg.from_hash(msg) if msg.is_a? Hash
process_msg(msg)
end
end
def process_msg(msg)
logger.info("Process Schedule")
vendor = msg.vendor
schedule_transform = for_vendor(vendor)
unless schedule_transform
@errors.push("ProcessSchedule: Unknown vendor #{vendor}")
return
end
content_map = []
msg.vendor_schedule[:contents].each do |content|
# TODO: optimization: first check whether the content has already been ingested into VLE
vle_asset = create_asset(content)[:asset]
content_map << vle_asset
ingest_content(content, vle_asset[:meta][:presigned_url])
end
schedule = schedule_transform.call(vendor, msg.player, msg.vendor_schedule, content_map)
@out_queue.push(schedule)
true
rescue StandardError => e
@errors.push(e.message)
end
private
def create_asset(content)
sanitized_name = content[:name].gsub(/\s+/, '_')
Auth::Client::Request::AuthAwareRequest.new(
Vle::VleCreateAsset.new(
{
name: sanitized_name,
file: sanitized_name,
project_id: Vle::VleSettings.instance.target_project
}
),
@tokens
).call
end
def ingest_content(content, destination)
Vle::VleIngestAsset.new.call(content, destination)
end
def for_vendor(vendor)
vendor_map[vendor&.to_sym]
end
def vendor_map
@vendor_map ||= {
broad_sign: Vendors::BroadSign::BroadSignTransformSchedule.new,
vistar: Vendors::Vistar::VistarTransformSchedule.new
}
end
end
end

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative '../log/loggable'
require_relative '../vle/vle_vendor_schedule'
require_relative 'models/schedule'
require_relative '../auth/auth_token_service'
require 'auth/client/request/auth_aware_request'
module SchedulePipeline
class PublishSchedule
include Log::Loggable
def initialize(queue, errors, tokens = Auth::AuthTokenService.instance)
@queue = queue
@errors = errors
@tokens = tokens
end
def start
@queue.subscribe do |msg|
msg = Models::Schedule.from_hash(msg) if msg.is_a? Hash
process_msg(msg)
end
end
def process_msg(msg)
logger.info("Publishing schedule: #{msg.name}...")
Auth::Client::Request::AuthAwareRequest.new(
Vle::VleVendorSchedule.new(
msg.vendor,
msg.player,
{ name: msg.name, start_time: msg.start_time, items: msg.to_hash[:items] }
),
@tokens
).call
logger.info("Published schedule: #{msg.name}")
rescue StandardError => e
@errors.push(e.message)
end
end
end

View File

@@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative '../../amqp/amqp_service'
module SchedulePipeline
module Queue
class BunnyQueue
def initialize(destination)
@destination = destination
end
def push(msg)
amqp.default_exchange.publish(msg.to_json, routing_key: @destination)
end
def subscribe(&block)
amqp_queue.subscribe do |_delivery_info, _metadata, payload|
block.call(JSON.parse(payload).with_indifferent_access)
end
end
private
def amqp
Amqp::AmqpService.instance
end
def amqp_queue
@queue ||= amqp.queue(@destination.to_s, auto_delete: false, durable: true)
end
end
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
require_relative 'bunny_queue'
module SchedulePipeline
module Queue
class BunnyQueueFactory
include Singleton
def for_name(name)
BunnyQueue.new(name)
end
end
end
end

View File

@@ -0,0 +1,44 @@
# frozen_string_literal: true
require_relative '../log/loggable'
require_relative 'errors'
require_relative 'publish_schedule'
require_relative 'process_schedule'
require_relative 'fetch_schedule'
module SchedulePipeline
# Schedule Processing Pipeline
class SchedulePipeline
include Log::Loggable
def initialize(queue_factory)
errors = Errors.new
processed_schedules_queue = queue_factory.for_name(:processed_schedules)
publish_schedule = PublishSchedule.new(processed_schedules_queue, errors)
unprocessed_schedules_queue = queue_factory.for_name(:unprocessed_schedules)
process_schedule = ProcessSchedule.new(unprocessed_schedules_queue, processed_schedules_queue, errors)
fetch_schedule_queue = queue_factory.for_name(:fetch_vendor_schedules)
fetch_schedule = FetchSchedule.new(fetch_schedule_queue, unprocessed_schedules_queue, errors)
@queues = [processed_schedules_queue, unprocessed_schedules_queue, fetch_schedule_queue]
@stages = [publish_schedule, process_schedule, fetch_schedule]
end
def start
stages.each(&:start)
logger.info "Started Schedule Pipeline"
end
def stop
stages.reverse_each(&:stop)
end
private
def stages
@stages
end
def queues
@queues
end
end
end

View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
require_relative '../../log/loggable'
require_relative '../errors/schedule_fetch_error'
require 'net/http'
module Vendors
module BroadSign
class BroadSignFetchSchedule
include Log::Loggable
AIR_DOMAIN = 'air.broadsign.com'
PATH = 'playlist/v1/generate'
def initialize(tokens)
@tokens = tokens
end
def call(params)
params = ActiveSupport::HashWithIndifferentAccess.new(
screen: '1',
duration: 3600
).merge(params_whitelist(params))
logger.info "BroadSign fetch schedule request for player #{params[:player]}"
url = URI.join("https://#{AIR_DOMAIN}", PATH)
res = Net::HTTP.post(
url,
{
player_identifier: params[:player].to_s,
screen_identifier: params[:screen]&.to_s,
duration: "#{params[:duration]}s"
}.to_json,
{
"Authorization": "Bearer #{@tokens.auth_token}",
"Content-Type": 'application/json'
}
)
case res
when Net::HTTPSuccess
JSON.parse(res.body).with_indifferent_access
else
logger.error "Error fetching BroadSign Air schedule: #{res.code}"
raise Vendors::Errors::ScheduleFetchError, { player: params[:player], response_code: res.code }.to_json
end
end
private
def params_whitelist(params)
params.slice(
:player,
:screen,
:duration
)
end
end
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
require_relative '../../log/loggable'
module Vendors
module BroadSign
class BroadSignTokens
include Singleton
def auth_token
AppConfig.get_mandatory 'vendors.broad_sign.token'
end
end
end
end

View File

@@ -0,0 +1,47 @@
# frozen_string_literal: true
require_relative '../../log/loggable'
require_relative '../../schedule_pipeline/models/schedule'
require_relative '../../schedule_pipeline/models/schedule_item'
require_relative '../errors/schedule_transform_error'
module Vendors
module BroadSign
class BroadSignTransformSchedule
include Log::Loggable
def call(vendor, player, schedule, content_map)
logger.debug("BroadSign schedule transform for vendor #{vendor}, player #{player}")
start_time = nil
# for performance reasons, here we truncate the items list to the first N items where N is the number of contents
# in the schedule. This assumes the schedule round-robins content!
truncate_schedule(schedule, content_map.size)
items = schedule[:items].collect do |item|
start_time ||= item[:startTime]
SchedulePipeline::Models::ScheduleItem.new(
item[:duration],
content_map[item[:contentIndex] || 0][:id],
# TODO: build full POP reporting url using this token
item[:token]
)
end
raise Vendors::Errors::ScheduleTransformError, 'Schedule without a start time' if start_time.nil?
SchedulePipeline::Models::Schedule.new(
"BroadSign schedule for player #{player}",
vendor,
player,
start_time,
items
)
end
private
def truncate_schedule(schedule, size)
schedule[:items] = schedule[:items].first(size)
end
end
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Vendors
module Errors
class ContentIngestError < StandardError; end
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Vendors
module Errors
class ScheduleFetchError < StandardError; end
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Vendors
module Errors
class SchedulePublishError < StandardError; end
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Vendors
module Errors
class ScheduleTransformError < StandardError; end
end
end

View File

@@ -0,0 +1,93 @@
# frozen_string_literal: true
require_relative '../../log/loggable'
require_relative '../errors/schedule_fetch_error'
require 'net/http'
module Vendors
module Vistar
class VistarFetchAd
include Log::Loggable
PATH = 'api/v1/get_ad/json'
DEFAULT_SUPPORTED_MEDIA = %w[
application/x-shockwave-dynamic-flash
application/x-shockwave-flash
image/jpeg
image/png
video/mp4
video/mpeg
video/mpg
video/quicktime
video/webm
video/x-flv
video/x-ms-wmv
video/x-msvideo
]
def initialize(tokens)
@tokens = tokens
end
def call(params)
logger.info "Vistar fetch Ad request"
res = Net::HTTP.post(
Vendors::Vistar::VistarSettings.instance.vistar_url(PATH),
body(params),
{
"Content-Type": 'application/json'
}
)
case res
when Net::HTTPSuccess
JSON.parse(res.body).with_indifferent_access
else
logger.error "Error fetching Vistar Ad: #{res.code}"
raise Vendors::Errors::ScheduleFetchError, { response_code: res.code }.to_json
end
end
private
def body(params)
display_area = (params[:display_area] || [{}]).each_with_index.collect do |item, idx|
{
id: "display-#{idx}",
width: 1080,
height: 1920,
supported_media: DEFAULT_SUPPORTED_MEDIA,
static_duration: 8
}.merge(item)
end
params_whitelist(params).merge({
network_id: Vendors::Vistar::VistarSettings.instance.network_id,
api_key: "#{@tokens.api_key}",
direct_connection: false,
display_area: display_area
}).to_json
end
def params_whitelist(params)
params.slice(
:device_id,
:venue_id,
:display_time,
:device_attribute,
:name,
:display_area,
:id,
:width,
:height,
:allow_audio,
:supported_media,
:min_duration,
:max_duration,
:order_id,
:max_file_size_bytes,
:static_duration,
:latitude,
:longitude
)
end
end
end
end

View File

@@ -0,0 +1,37 @@
module Vendors
module Vistar
class VistarFetchSchedule
def initialize(tokens)
@tokens = tokens
end
def call(params)
fetch = fetch_ad(params)
return nil unless fetch && fetch[:advertisement]
ad = fetch[:advertisement][0]
{
contents: [
{
name: "vistar_asset_#{ad[:asset_id]}",
url: ad[:asset_url]
}
],
startTime: Time.at(ad[:display_time]).to_datetime,
items: [
{
contentIndex: 0,
duration: "#{ad[:length_in_seconds]}s",
pop_url: ad[:proof_of_play_url]
}
]
}
end
private
def fetch_ad(params)
Vendors::Vistar::VistarFetchAd.new(@tokens).call(params)
end
end
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
module Vendors
module Vistar
class VistarSettings
include Singleton
def vistar_url(path)
URI::join(
AppConfig.get_mandatory('vendors.vistar.base_url'),
path
)
end
def network_id
AppConfig.get_mandatory('vendors.vistar.network_id')
end
end
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
require_relative '../../log/loggable'
module Vendors
module Vistar
class VistarTokens
include Singleton
def api_key
AppConfig.get_mandatory 'vendors.vistar.api_key'
end
end
end
end

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
require_relative '../../log/loggable'
require_relative '../../schedule_pipeline/models/schedule'
require_relative '../../schedule_pipeline/models/schedule_item'
require_relative '../errors/schedule_transform_error'
module Vendors
module Vistar
class VistarTransformSchedule
include Log::Loggable
def call(vendor, player, schedule, content_map)
logger.debug("Vistar schedule transform for vendor #{vendor}, player #{player}")
start_time = schedule[:startTime]
items = schedule[:items].collect do |item|
SchedulePipeline::Models::ScheduleItem.new(
item[:duration],
content_map[item[:contentIndex] || 0][:id],
item[:pop_url]
)
end
raise Vendors::Errors::ScheduleTransformError, 'Schedule without a start time' if start_time.nil?
SchedulePipeline::Models::Schedule.new(
"Vistar schedule for player #{player}",
vendor,
player,
start_time,
items
)
end
end
end
end

View File

@@ -0,0 +1,52 @@
# frozen_string_literal: true
require_relative '../log/loggable'
require_relative '../vendors/errors/content_ingest_error'
require_relative 'vle_settings'
module Vle
class VleCreateAsset
include Log::Loggable
PATH = 'v1/assets'
def initialize(asset)
@asset = asset
end
def url
Vle::VleSettings.instance.central_url(PATH)
end
def call(access_token)
logger.info("ONEX create asset: #{asset}")
res = Net::HTTP.post(
url,
{
file: asset[:file],
name: asset[:name],
project_id: asset[:project_id],
description_short: "vendor-scheduler-service content: #{asset[:name]}"
}.to_json,
{
"Authorization": "Bearer #{access_token}",
"Content-Type": 'application/json'
}
)
case res
when Net::HTTPSuccess
JSON.parse(res.body).with_indifferent_access
when Net::HTTPUnauthorized
raise Auth::Client::Errors::Unauthorized, 'Unauthorized'
else
logger.error "Error creating asset in VLE: #{res.code}"
raise Vendors::Errors::ContentIngestError
end
end
private
def asset
@asset
end
end
end

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'rest-client'
require_relative '../log/loggable'
require_relative '../vendors/errors/content_ingest_error'
module Vle
class VleIngestAsset
include Log::Loggable
def call(content, destination)
logger.info("ONEX asset ingest: #{content}")
tmp_file = Tempfile.new('vendor-scheduler-service-content-ingest')
begin
source = content[:url] || content[:uri]
IO.copy_stream(URI.parse(source).open, tmp_file.path)
logger.debug("Content retrieved. Now starting ONEX ingest...")
res = RestClient.put(destination, tmp_file, { 'Content-Type': "binary/octet-stream" })
case res.code
when 200 #Net::HTTPSuccess
logger.debug("ONEX Asset ingested.")
true
else
logger.error "Error ingesting content into ONEX: POST returned #{res.code}"
raise Vendors::Errors::ContentIngestError
end
rescue StandardError => e
logger.error "Error ingesting content into ONEX: #{e.message}"
raise Vendors::Errors::ContentIngestError
ensure
tmp_file.close!
end
end
end
end

View File

@@ -0,0 +1,25 @@
# frozen_string_literal: true
module Vle
class VleSettings
include Singleton
def target_project
AppConfig.safe_get('onex.target_project_id') || '1'
end
def central_url(path)
URI::join(
AppConfig.safe_get('onex.central_url') || 'http://localhost:5070',
path
)
end
def scheduler_url(path)
URI::join(
AppConfig.safe_get('onex.scheduler_url') || 'http://localhost:5040',
path
)
end
end
end

View File

@@ -0,0 +1,46 @@
# frozen_string_literal: true
require_relative '../log/loggable'
require_relative '../vendors/errors/schedule_publish_error'
module Vle
class VleVendorSchedule
include Log::Loggable
PATH = 'v1/vendor_schedule'
def initialize(vendor, player, schedule)
@vendor = vendor
@player = player
@schedule = schedule
end
def url
Vle::VleSettings.instance.scheduler_url(PATH)
end
def call(access_token)
res = Net::HTTP.post(
url,
{
vendor: @vendor,
player: @player,
schedule: @schedule
}.to_json,
{
"Authorization": "Bearer #{access_token}",
"Content-Type": 'application/json'
}
)
case res
when Net::HTTPSuccess
JSON.parse(res.body).with_indifferent_access
when Net::HTTPUnauthorized
raise Auth::Client::Errors::Unauthorized, 'Unauthorized'
else
logger.error "Error (#{res.code}) scheduling vendor content in ONEX: #{res.body}"
raise Vendors::Errors::SchedulePublishError
end
end
end
end