Skip to content

Instantly share code, notes, and snippets.

@DariaUlanova
Last active May 29, 2017 09:36
Show Gist options
  • Save DariaUlanova/74ab2e267e8579e3ce59d706f816af1c to your computer and use it in GitHub Desktop.
Save DariaUlanova/74ab2e267e8579e3ce59d706f816af1c to your computer and use it in GitHub Desktop.

Releadgion.com is a one place where people buy mobile and web advertising from many platforms such as Google Adwords, Facebook.

One of the platform is MyTarget.com

There was a task to realize usage of MyTarget leading ad campaigns through Releadgion UI.

(create campaigns, ads, set up ad targetings and etc.)

There is a base class MyTarget::Api::ApiBase whitch is responsible for MyTarget API.

And there are inherited classes for campaigns entities (account, ads, etc.)

For example here MyTarget::Api::Banners there are methods to create, update, delete ads.

To test API I used gem VCR .

# https://target.my.com/doc/api/oauth2
class MyTarget::Api::ApiBase
extend DslAttribute
TIMEOUT = 120
OPEN_TIMEOUT = 30
dsl_attribute :api_version, :v1
dsl_attribute :mutex_with_ids, false
REQUEST_MUTEX_EXPIRE = (TIMEOUT + OPEN_TIMEOUT).seconds
MUTEX_OPTIONS = {
block: 30.seconds,
expire: REQUEST_MUTEX_EXPIRE
}
def api_request method, path, params = {}
status, data = RedisMutex.with_lock(redis_key(path), MUTEX_OPTIONS) do
MyTarget::Api::Logger.instance.info(self, method, path, params) do
perform_request method, path, params
end
end
return data if status == 200
raise ApiError.new("#{status} #{data.to_json}", data)
end
def access_token
return existing_token if existing_token
token_data = MyTarget::Api::Tokens.call(token_username)
cache_token token_data
token_data[:access_token]
end
private
def perform_request method, path, params, attempt = 0
response = send "#{method}_request", path_to_url(path), params
# ApiError: 401 "Unknown access token"
if response.status == 401 && attempt.zero?
expire_token
perform_request method, path, params, attempt + 1
else
[
response.status,
JSON.parse(response.body, symbolize_names: true, quirks_mode: true)
]
end
end
def get_request url, params
Faraday.get(url, params) do |request|
request.headers['Authorization'] = "Bearer #{access_token}"
request.options.timeout = TIMEOUT
request.options.open_timeout = OPEN_TIMEOUT
end
end
def post_request url, params
Faraday.post(url) do |request|
request.headers['Authorization'] = "Bearer #{access_token}"
request.body = params.to_json
request.options.timeout = TIMEOUT
request.options.open_timeout = OPEN_TIMEOUT
end
end
def file_request url, params
conn = Faraday.new do |f|
f.request :multipart
f.request :url_encoded
f.adapter Faraday.default_adapter
end
file_key = params.keys.find { |v| v.to_s =~ /(\b|_)file(\b|_)/ }
file_field = params[file_key]
# NOTE раскоментировать этот вариант при выполнении таска rake entity:my_target
# params[file_key] = Faraday::UploadIO.new params[file_key], 'image/jpg'
S3AttachmentProxy.new(file_field).proxify do |file|
params[file_key] = Faraday::UploadIO.new(
file.path, params[file_key].content_type
)
conn.post(url, params) do |request|
request.headers['Authorization'] = "Bearer #{access_token}"
request.options.timeout = TIMEOUT
request.options.open_timeout = OPEN_TIMEOUT
end
end
ensure
# нужно восстанавливать оригинальное значение, запрос может пойти повторно
params[file_key] = file_field
end
def credentials
Rails.application.secrets.oauth[:my_target]
end
def path_to_url path
credentials[:"api_#{api_version}_url"] + path + '.json'
end
def token_username
MyTarget::Api::Tokens::MAIN_USERNAME
end
def token_cache_key
[:api_token, :my_target, :access_token]
end
def existing_token
Rails.cache.read token_cache_key
end
def expire_token
Rails.cache.delete token_cache_key
end
def cache_token token_data
Rails.cache.write(
token_cache_key,
token_data[:access_token],
expires_in: token_data[:expires_in].seconds - 1.minute
)
token_data[:access_token]
end
def redis_key path
path_key = mutex_with_ids ? path : path.gsub(/\d+/, 'ID')
[:api, :my_target, @agency_client_name, path_key].select(&:present?).to_json
end
end
class MyTarget::Api::ClientApiBase < MyTarget::Api::ApiBase
pattr_initialize :agency_client_name
private
def token_cache_key
[:api_token, :my_target, @agency_client_name]
end
def token_username
@agency_client_name
end
end
# https://target.my.com/doc/api/detailed/#resource_banner
class MyTarget::Api::Banners < MyTarget::Api::ClientApiBase
def create campaign_id:, attributes:
api_result = api_request(
:post,
"/campaigns/#{campaign_id}/banners",
attributes
)
MyTarget::BannerData.new api_result
end
def index
api_request(:get, '/banners', {}).map do |entry|
MyTarget::BannerData.new entry
end
end
def campaign_banners campaign_id
api_request(:get, "/campaigns/#{campaign_id}/banners", {})
.map { |entry| MyTarget::BannerData.new entry }
end
def show banner_id
MyTarget::BannerData.new api_request(:get, "/banners/#{banner_id}", {})
end
def update banner_id:, attributes:
api_result = api_request :post, "/banners/#{banner_id}", attributes
MyTarget::BannerData.new api_result
end
def destroy banner_id
update banner_id: banner_id, attributes: { status: 'deleted' }
end
end
describe MyTarget::Api::Banners, vcr: true do
let(:api) { MyTarget::Api::Banners.new mt_banner.api_key }
let(:mt_banner) { seed :mt_banner }
let(:mt_campaign) { create :mt_campaign, :synced_1080x607 }
describe '#index' do
subject { api.index }
it { is_expected.to have_at_least(4).items }
its(:first) { is_expected.to be_kind_of MyTarget::BannerData }
end
describe '#create' do
let(:text) { 'text' }
let(:title) { 'title' }
describe 'web mobile traffic' do
let(:icon_api) { MyTarget::Api::AppIcon.new mt_banner.api_key }
let(:synced_image) { build :mt_image_promo, :synced_1080x607 }
let(:icon) { icon_api.fetch :ios, '829628894' }
let(:url) { 'https://itunes.apple.com/ru/app/oh-my-master!-ekspertnyj-podhod/id829628894?mt=8' }
let(:attributes) do
{
text: text,
title: title,
call_to_action: 'install',
url: url,
content: {
image: { id: 338 },
promo_image: { id: synced_image.external_id }
}
}
end
subject(:api_result) do
api.create campaign_id: '369270', attributes: attributes
end
it { expect(api_result.id.to_s).to match(/\d{6}/) }
end
describe 'web desktop traffic' do
let(:mt_campaign) { create :mt_campaign, :synced_web_desktop }
let(:mt_image_promo_desktop) do
build :mt_image_promo_desktop, :synced_web_desktop
end
let(:mt_image_logo) { build :mt_image_logo, :synced_web_desktop }
let(:mt_image_vk_feed) { build :mt_image_vk_feed, :synced_web_desktop }
let(:url) { 'http://test.ru/' }
let(:attributes) do
{
text: text,
title: title,
url: url,
content: {
promo_image: { id: mt_image_promo_desktop.external_id },
image: { id: mt_image_logo.external_id },
vk_feed: { id: mt_image_vk_feed.external_id }
}
}
end
subject(:api_result) do
api.create campaign_id: mt_campaign.external_id, attributes: attributes
end
it { expect(api_result.id.to_s).to match(/\d{6}/) }
end
end
describe '#campaign_banners' do
subject { api.campaign_banners mt_campaign.external_id }
it { is_expected.to have_at_least(1).items }
its(:first) { is_expected.to be_kind_of MyTarget::BannerData }
end
describe '#show' do
let(:banner_id) { api.index.first.id }
subject { api.show banner_id }
it { is_expected.to be_kind_of MyTarget::BannerData }
end
describe '#update' do
let(:banner_id) { api.index.first.id }
let(:attributes) { { status: 'blocked', url: 'http://test.ru/' } }
subject(:api_result) { api.update banner_id: banner_id, attributes: attributes }
it do
is_expected.to be_kind_of MyTarget::BannerData
expect(api_result.status).to eq attributes[:status]
end
end
describe '#destroy' do
it do
expect(api.destroy(826_154)).to have_attributes(
status: 'deleted',
moderation_status: 'allowed',
disapproval_reasons: []
)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment