class Twitter < ApplicationRecord self.table_name = "twitter_tokens" validates_presence_of :marshaled_client, :marshaled_token class RetryableTwitterApiError < StandardError; end # For testing def self.user_lookup poll_api( url: "https://api.twitter.com/2/users/me", method: :get, successful_response_code: 200, params: {} ) end def self.tweet(tweet) poll_api( url: "https://api.twitter.com/2/tweets", method: :post, successful_response_code: 201, params: { body: { text: tweet, }.to_json } ) end private_class_method def self.poll_api(url:, method:, successful_response_code:, params:) attempts ||= 0 attempts += 1 instance = self.first options = { method: method, headers: { "Content-Type" => "application/json", Authorization: "Bearer #{instance.access_token}" }, }.merge(params) request = Typhoeus::Request.new(url, options) response = request.run if response.code == 401 && attempts < 2 raise RetryableTwitterApiError end raise "#{response.code} response code received polling #{url}" if response.code != successful_response_code JSON.parse(response.body) rescue RetryableTwitterApiError instance.refresh_token! retry end delegate :access_token, to: :token def refresh_token! client.refresh_token = token.refresh_token new_token = client.access_token! update( marshaled_client: Marshal.dump(client), marshaled_token: Marshal.dump(new_token), ) end private #TODO: don't use Marshal.load here, it violates a security concern according to rubocop def client @client ||= Marshal.load(marshaled_client) end def token @token ||= Marshal.load(marshaled_token) end end