Browse files

move fetching Twitter/Facebook data into ServiceFetcher

Handles being unauthorized by removing stored OAuth tokens.

Also improves fetching/refreshing Twitter picture.
  • Loading branch information...
1 parent 1c76bad commit 903090d58a454be631d29cd258422b62c96176fc @mislav committed Feb 4, 2012
Showing with 222 additions and 54 deletions.
  1. +75 −0 app/models/service_fetcher.rb
  2. +47 −54 app/models/user/social.rb
  3. +100 −0 spec/models/user_spec.rb
View
75 app/models/service_fetcher.rb
@@ -0,0 +1,75 @@
+require 'net/http'
+require 'json'
+require 'oauth'
+require 'oauth2'
+require 'hashie/mash'
+
+module ServiceFetcher
+
+ extend self
+
+ def twitter_config
+ Movies::Application.config.twitter
+ end
+
+ def twitter_client(token_values)
+ token, secret, = token_values
+ oauth = OAuth::Consumer.new twitter_config.consumer_key, twitter_config.secret,
+ site: 'https://api.twitter.com'
+ OAuth::AccessToken.new(oauth, token, secret)
+ end
+
+ def get_twitter_friends(token_values)
+ client = twitter_client(token_values)
+ response = client.get('/1/friends/ids.json')
+ process_json_response(response.body, response.code.to_i)
+ rescue => error
+ error
+ end
+
+ def facebook_config
+ Movies::Application.config.facebook
+ end
+
+ def facebook_client(token_values)
+ token, = token_values
+ oauth = OAuth2::Client.new facebook_config.app_id, facebook_config.secret,
+ site: 'https://graph.facebook.com',
+ raise_errors: false
+
+ OAuth2::AccessToken.new(oauth, token, mode: :query, param_name: 'access_token')
+ end
+
+ def get_facebook_info(token_values, params)
+ client = facebook_client(token_values)
+ response = client.get('/me', params: params)
+ process_json_response(response.body, response.status)
+ rescue => error
+ error
+ end
+
+ def get_twitter_profile_image(screen_name)
+ get_redirect_location "http://api.twitter.com/1/users/profile_image/#{screen_name}?size=bigger"
+ end
+
+ private
+
+ def process_json_response(body, status)
+ data = JSON.parse body rescue $!
+ data = Hashie::Mash.new data if data.is_a? Hash
+
+ data.singleton_class.class_eval "def status() #{status} end"
+ data
+ end
+
+ def get_redirect_location(url)
+ return nil if Movies.offline?
+ url = URI.parse url unless url.respond_to? :request_uri
+ response = Net::HTTP.start(url.host, open_timeout: 2) {|http| http.get url.request_uri }
+ response['location'] if response.is_a? Net::HTTPRedirection
+ rescue Timeout::Error
+ NeverForget.log($!, url: url)
+ return nil
+ end
+
+end
View
101 app/models/user/social.rb
@@ -1,8 +1,3 @@
-require 'net/http'
-require 'json'
-require 'oauth'
-require 'oauth2'
-
module User::Social
extend ActiveSupport::Concern
@@ -47,39 +42,45 @@ def refresh_social_connections
def fetch_twitter_friends
if self['twitter_token']
- client = ::User::Social.twitter_client(self['twitter_token'])
- response = client.get('/1/friends/ids.json')
- ids_data = JSON.parse response.body
- self.twitter_friends = ids_data['ids']
+ data = ServiceFetcher.get_twitter_friends(self['twitter_token'])
+ if data.respond_to? :status
+ case data.status
+ when 200
+ self.twitter_friends = data.ids
+ when 401
+ # the token seems no longer valid
+ self['twitter_token'] = nil
+ else
+ raise "unhandled status: #{data.status.inspect}"
+ end
+ elsif data.is_a? Exception
+ raise data
+ end
end
- end
-
- def self.twitter_client(token_values)
- config = Movies::Application.config.twitter
- token, secret, = token_values
- oauth = OAuth::Consumer.new config.consumer_key, config.secret,
- site: 'https://api.twitter.com'
- OAuth::AccessToken.new(oauth, token, secret)
+ rescue StandardError
+ NeverForget.log($!, user_id: self.id)
end
def fetch_facebook_friends
if self['facebook_token']
- client = ::User::Social.facebook_client(self['facebook_token'])
- response = client.get('/me', params: {fields: 'friends'}) # 'movies,friends'
- user_info = JSON.parse response.body
- self.facebook_friends = user_info['friends']['data'].map { |f| f['id'] }
- # watched.import_from_facebook user_info['movies']['data']
+ data = ServiceFetcher.get_facebook_info(self['facebook_token'], fields: 'friends')
+ if data.respond_to? :status
+ case data.status
+ when 200
+ self.facebook_friends = data.friends.data.map(&:id)
+ # watched.import_from_facebook data.movies.data
+ when 401
+ # the token seems no longer valid
+ self['facebook_token'] = nil
+ else
+ raise "unhandled status: #{data.status.inspect}"
+ end
+ elsif data.is_a? Exception
+ raise data
+ end
end
- end
-
- def self.facebook_client(token_values)
- config = Movies::Application.config.facebook
- token, = token_values
- oauth = OAuth2::Client.new config.app_id, config.secret,
- site: 'https://graph.facebook.com',
- token_url: '/oauth/access_token'
-
- OAuth2::AccessToken.new(oauth, token, mode: :query, param_name: 'access_token')
+ rescue StandardError
+ NeverForget.log($!, user_id: self.id)
end
def twitter_url
@@ -88,15 +89,20 @@ def twitter_url
# 73x73 px
def twitter_picture
- self['twitter_picture'] || begin
- name = self['twitter']['screen_name']
- img = get_redirect_target "http://api.twitter.com/1/users/profile_image/#{name}?size=bigger"
- if img and img !~ /default_profile_/
- self['twitter_picture'] = img
- self.save
- img
- end
- end
+ update_twitter_picture(:autosave) if twitter_picture_stale?
+ self['twitter_picture']
+ end
+
+ def update_twitter_picture(autosave = false)
+ img = ServiceFetcher.get_twitter_profile_image self['twitter']['screen_name']
+ self['twitter_picture'] = img =~ /default_profile_/ ? nil : img
+ self['twitter_picture_updated_at'] = Time.now
+ self.save if autosave
+ end
+
+ def twitter_picture_stale?
+ self['twitter_picture_updated_at'].nil? or
+ self['twitter_picture_updated_at'] < 1.day.ago
end
def facebook_url
@@ -152,17 +158,4 @@ def login_from_provider(auth, current_user = nil)
return user
end
end
-
- private
-
- def get_redirect_target(url)
- return nil if Movies.offline?
- # TODO: cache results for 1 day
- url = URI.parse url unless url.respond_to? :request_uri
- response = Net::HTTP.start(url.host, open_timeout: 2) {|http| http.get url.request_uri }
- response['location'] if response.is_a? Net::HTTPRedirection
- rescue Timeout::Error
- NeverForget.log($!, url: url)
- return nil
- end
end
View
100 spec/models/user_spec.rb
@@ -1,5 +1,7 @@
# encoding: utf-8
require 'spec_helper'
+require 'ostruct'
+require 'hashie/mash'
describe User do
before do
@@ -280,4 +282,102 @@ def join_collection_create(user, doc)
@user.should_not be_following_on_twitter(user2)
end
end
+
+ describe "fetching friends lists" do
+ let(:user) do
+ build.tap { |user|
+ user.twitter_friends = [234]
+ user.facebook_friends = [234]
+ user['twitter_token'] = ['TWTOKEN', 'TWSECRET']
+ user['facebook_token'] = ['FBTOKEN', nil]
+ }
+ end
+
+ it "successful from twitter" do
+ twitter_data = OpenStruct.new status: 200, ids: [1234, 4567]
+
+ ServiceFetcher.should_receive(:get_twitter_friends).
+ with(user['twitter_token']).
+ and_return(twitter_data)
+
+ user.fetch_twitter_friends
+ user.twitter_friends.to_a.should =~ [1234, 4567]
+ user['twitter_token'].should_not be_nil
+ end
+
+ it "failed from twitter" do
+ twitter_data = OpenStruct.new status: 401
+
+ ServiceFetcher.should_receive(:get_twitter_friends).
+ with(user['twitter_token']).
+ and_return(twitter_data)
+
+ user.fetch_twitter_friends
+ user.twitter_friends.to_a.should =~ [234]
+ user['twitter_token'].should be_nil
+ end
+
+ it "successful from facebook" do
+ facebook_data = Hashie::Mash.new status: 200,
+ friends: {
+ data: [ {id: 1234}, {id: 4567} ]
+ }
+
+ ServiceFetcher.should_receive(:get_facebook_info).
+ with(user['facebook_token'], fields: 'friends').
+ and_return(facebook_data)
+
+ user.fetch_facebook_friends
+ user.facebook_friends.to_a.should =~ ["1234", "4567"]
+ user['facebook_token'].should_not be_nil
+ end
+
+ it "failed from facebook" do
+ facebook_data = OpenStruct.new status: 401
+
+ ServiceFetcher.should_receive(:get_facebook_info).
+ with(user['facebook_token'], fields: 'friends').
+ and_return(facebook_data)
+
+ user.fetch_facebook_friends
+ user.facebook_friends.to_a.should =~ ["234"]
+ user['facebook_token'].should be_nil
+ end
+ end
+
+ describe "picture" do
+ let(:user) { build }
+
+ it "has no picture" do
+ user.picture_url.should be_nil
+ end
+
+ it "fetches picture from twitter" do
+ user['twitter'] = {'id' => 1, 'screen_name' => 'mislav'}
+
+ ServiceFetcher.should_receive(:get_twitter_profile_image).
+ once.with('mislav').
+ and_return('http:///mislav.png')
+
+ user.picture_url.should eq('http:///mislav.png')
+ user.reload.picture_url.should eq('http:///mislav.png')
+ end
+
+ it "refreshes stale twitter picture" do
+ user['twitter'] = {'id' => 1, 'screen_name' => 'mislav'}
+ user['twitter_picture'] = 'http:///mislav.png'
+ user['twitter_picture_updated_at'] = 2.days.ago.utc
+
+ ServiceFetcher.should_receive(:get_twitter_profile_image).
+ once.with('mislav').
+ and_return('http:///new_pic.png')
+
+ user.picture_url.should eq('http:///new_pic.png')
+ end
+
+ it "uses facebook picture" do
+ user['facebook'] = {'id' => '1'}
+ user.picture_url.should eq('http://graph.facebook.com/1/picture?type=normal')
+ end
+ end
end

0 comments on commit 903090d

Please sign in to comment.