Skip to content

Commit

Permalink
Merge pull request #437 from voxmedia/google-update
Browse files Browse the repository at this point in the history
Google update
  • Loading branch information
ryanmark authored May 20, 2019
2 parents fb9ef20 + f156cdb commit 3623fb0
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 109 deletions.
1 change: 1 addition & 0 deletions app/controllers/autotune/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ def build_data
def create_google_doc
current_auth = current_user.authorizations.find_by!(:provider => 'google_oauth2')
google_client = GoogleDocs.new(
:user_id => current_auth.uid,
:refresh_token => current_auth.credentials['refresh_token'],
:access_token => current_auth.credentials['token'],
:expires_at => current_auth.credentials['expires_at']
Expand Down
10 changes: 5 additions & 5 deletions app/models/concerns/autotune/repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,15 @@ def sync_from_blueprint(update: false)
# Create a new repo object based on the projects working dir
project_dir = setup_shell

# check if the directory already exists on disk, make sure it's updated,
# or copy the code from the blueprint
if project_dir.exist?
if update || project_dir.version != blueprint_version
# Update the project files. Because of issue #218, due to
# some weirdness in git 1.7, we can't just update the repo.
# We have to make a new copy.
project_dir.rm
blueprint_dir.copy_to(project_dir.working_dir)
elsif project_dir.version == blueprint_version
# if we're not updating, bail if we have the files
return
end
else
# Copy the blueprint to the project working dir.
Expand All @@ -122,12 +121,13 @@ def sync_from_blueprint(update: false)
# Checkout correct version and branch
project_dir.commit_hash_for_checkout = blueprint_version
project_dir.update
# Make sure the environment is correct for this version
project_dir.setup_environment
# update the config
self.blueprint_config = project_dir.read(BLUEPRINT_CONFIG_FILENAME)
end

# Make sure the environment is correct for this version
project_dir.setup_environment

true
end
end
Expand Down
4 changes: 2 additions & 2 deletions autotune.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ Gem::Specification.new do |s|
s.add_dependency 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
s.add_dependency 'sass-rails', '>= 3.2'
s.add_dependency 'will_paginate', '~> 3.0.7'
s.add_dependency 'google-api-client', '~> 0.8.6'
s.add_dependency 'google-api-client', '~> 0.11'
s.add_dependency 'omniauth-google-oauth2', '~> 0.3.0'
s.add_dependency 'autoshell', '~> 1.0.3'
s.add_dependency 'autoshell', '~> 1.0.5'
s.add_dependency 'i18n-js', '>= 3.0.0.rc11'
s.add_dependency 'nokogiri', '~> 1.8.1'
s.add_dependency 'mail', '~> 2.6.6.rc1'
Expand Down
13 changes: 10 additions & 3 deletions lib/autotune/deployer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ def prep_target
rescue GoogleDocs::Forbidden => exc
logger.error(exc)
raise Autotune::Forbidden, 'Unable to retrieve Google Doc because user does not have access.'
rescue OAuth2::Error => exc
if exc.code == 'invalid_grant'
raise Autotune::Unauthorized, "Google Auth: #{exc.description}"
else
raise
end
end

def after_prep_target
Expand Down Expand Up @@ -210,6 +216,7 @@ def google_client
current_auth = user.authorizations.find_by!(:provider => 'google_oauth2')

google_client = GoogleDocs.new(
:user_id => current_auth.uid,
:refresh_token => current_auth.credentials['refresh_token'],
:access_token => current_auth.credentials['token'],
:expires_at => current_auth.credentials['expires_at']
Expand All @@ -231,11 +238,11 @@ def google_doc_contents(url)

cache_key = "googledoc#{doc_key}"

resp = google_client.find(doc_key)
file = google_client.find(doc_key)
cache_value = nil
if Rails.cache.exist?(cache_key)
cache_value = Rails.cache.read(cache_key)
needs_update = cache_value['version'] && resp['version'] != cache_value['version']
needs_update = cache_value['version'] && file.version != cache_value['version']
else
needs_update = true
end
Expand All @@ -244,7 +251,7 @@ def google_doc_contents(url)
if needs_update
google_client.share_with_domain(doc_key, Autotune.configuration.google_auth_domain)
ret = google_client.get_doc_contents(url)
Rails.cache.write(cache_key, 'ss_data' => ret, 'version' => resp['version'])
Rails.cache.write(cache_key, 'ss_data' => ret, 'version' => file.version)
else
ret = (cache_value || Rails.cache.read(cache_key))['ss_data']
end
Expand Down
182 changes: 83 additions & 99 deletions lib/autotune/google_docs.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
require 'uri'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/installed_app'
require 'google/api_client/auth/storage'
require 'google/api_client/auth/storages/file_store'
require 'googleauth'
require 'googleauth/user_authorizer'
require 'googleauth/token_store'
require 'google/apis/drive_v2'
require 'fileutils'
require 'json'
require 'date'
require 'stringio'

module Autotune
# Wrapper around the official google client, for grabbing content from google
# documents
class GoogleDocs
attr_reader :client

def self.parse_url(url)
url.match(/^(?<base_url>https:\/\/docs.google.com\/(?:a\/(?<domain>[^\/]+)\/)?(?<type>[^\/]+)\/d\/(?<id>[-\w]{25,})).+$/)
url.match(%r{^(?<base_url>https:\/\/docs.google.com\/(?:a\/(?<domain>[^\/]+)\/)?(?<type>[^\/]+)\/d\/(?<id>[-\w]{25,})).+$})
end

def self.key_from_url(url)
Expand All @@ -26,21 +28,28 @@ def auth
end

def initialize(options)
@client = Google::APIClient.new

@client.authorization.update!({
scope = [
'https://www.googleapis.com/auth/drive',
'https://spreadsheets.google.com/feeds/'
]
client_id = Google::Auth::ClientId.new(ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'])
token_store = HashTokenStore.new(options[:user_id] => MultiJson.dump({
:client_id => ENV['GOOGLE_CLIENT_ID'],
:client_secret => ENV['GOOGLE_CLIENT_SECRET'],
:scope => 'https://www.googleapis.com/auth/drive ' \
'https://spreadsheets.google.com/feeds/'
}.update(options))

begin
@client.authorization.refresh! if @client.authorization.expired?
rescue Signet::AuthorizationError => exc
raise AuthorizationError, exc.message
:access_token => options[:access_token],
:refresh_token => options[:refresh_token],
:scope => scope,
:expiration_time_millis => options[:expires_at].to_i * 1000
}))
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)

credentials = authorizer.get_credentials(options[:user_id])
if credentials.nil?
raise AuthorizationError, 'Unable to obtain Google Authorization'
end

@client = Google::Apis::DriveV2::DriveService.new
@client.authorization = credentials

@_files = {}
@_spreadsheets = {}
end
Expand All @@ -62,7 +71,7 @@ def get_doc_contents(url, format: nil)
else
ret = export(parts['id'], format || :txt)
end
return ret
ret
end

# Find a Google Drive file
Expand All @@ -75,17 +84,10 @@ def get_doc_contents(url, format: nil)
def find(file_id)
return @_files[file_id] unless @_files[file_id].nil?

drive = @client.discovered_api('drive', 'v2')

# get the file metadata
resp = @client.execute(
:api_method => drive.files.get,
:parameters => { :fileId => file_id })

# die if there's an error
handle_errors resp
resp = @client.get_file(file_id)

@_files[file_id] = resp.data
@_files[file_id] = resp
end

# Export a file
Expand All @@ -95,27 +97,16 @@ def find(file_id)
# @param type [:excel, :text, :html] export type
# @return [String] file contents
def export(file_id, type)
# watch(file_id)
list_resp = find(file_id)

# decide which mimetype we want
mime = mime_for(type).content_type

# Grab the export url.
if list_resp['exportLinks'] && list_resp['exportLinks'][mime]
uri = list_resp['exportLinks'][mime]
else
raise "Google doesn't support exporting file id #{file_id} to #{type}"
end

# get the export
get_resp = @client.execute(:uri => uri)

# die if there's an error
handle_errors get_resp
# Create a buffer to write the file contents to`
io = StringIO.new
@client.export_file(file_id, mime, :download_dest => io)
ret = io.string

# contents
get_resp.body
io.string
end

# Export a file and save to disk
Expand All @@ -126,19 +117,13 @@ def export(file_id, type)
# @param filename [String] where to save the spreadsheet
# @return [String] path to the excel file
def export_to_file(file_id, type, filename = nil)
contents = export(file_id, type)

if filename.nil?
# get a temporary file. The export is binary, so open the tempfile in
# write binary mode
fp = Tempfile.create(
['googledoc', ".#{type}"], :binmode => mime_for(type.to_s).binary?)
filename = fp.path
fp.write(contents)
fp.close
else
open(filename, 'wb') { |f| f.write(contents) }
end
# decide which mimetype we want
mime = mime_for(type).content_type

filename ||= "#{Dir.tmpdir}/googledoc-#{file_id}-#{Time.now.to_i}.#{type}"

@client.export_file(file_id, mime, :download_dest => filename)

filename
end

Expand All @@ -148,24 +133,19 @@ def export_to_file(file_id, type, filename = nil)
# @param title [String] title for the newly created file
# @return [Hash] hash containing the id/key and url of the new file
def copy(file_id, title = nil, visibility = :private)
drive = @client.discovered_api('drive', 'v2')

if title.nil?
copied_file = drive.files.copy.request_schema.new
copied_file = Google::Apis::DriveV2::File.new
else
copied_file = drive.files.copy.request_schema.new(
'title' => title, 'writersCanShare' => true)
copied_file = Google::Apis::DriveV2::File.new(
:title => title, :writers_can_share => true)
end
cp_resp = @client.execute(
:api_method => drive.files.copy,
:body_object => copied_file,
:parameters => {
:fileId => file_id, :visibility => visibility.to_s.upcase })

# die if there's an error
handle_errors cp_resp
new_file = @client.copy_file(
file_id,
copied_file,
:visibility => visibility.to_s.upcase
)

{ :id => cp_resp.data['id'], :url => cp_resp.data['alternateLink'] }
{ :id => new_file.id, :url => new_file.alternate_link }
end
alias_method :copy_doc, :copy

Expand All @@ -176,44 +156,25 @@ def share_with_domain(file_id, domain)
end

def check_permission(file_id, domain)
drive = @client.discovered_api('drive', 'v2')
cp_resp = @client.execute(
:api_method => drive.permissions.list,
:parameters => { :fileId => file_id })
perms = @client.list_permissions(file_id)

has_permission = false
cp_resp.data.items.each do |item|
if item['type'] == 'domain' && item['domain'] == domain
perms.items.each do |perm|
if perm.type == 'domain' && perm.domain == domain
has_permission = true
elsif item['type'] == 'anyone'
elsif perm.type == 'anyone'
has_permission = true
end
end

if cp_resp.error?
raise CreateError, cp_resp.error_message
else
return has_permission
end
has_permission
end

def insert_permission(file_id, value, perm_type, role)
drive = @client.discovered_api('drive', 'v2')
new_permission = {
'value' => value,
'type' => perm_type,
'role' => role
}
cp_resp = @client.execute(
:api_method => drive.permissions.insert,
:body_object => new_permission,
:parameters => { :fileId => file_id })

if cp_resp.error?
raise CreateError, cp_resp.error_message
else
return cp_resp.data
end
new_permission = Google::Apis::DriveV2::Permission.new(
:value => value, :type => perm_type, :role => role
)
@client.insert_permission(file_id, new_permission)
end

# Get the mime type from a file extension
Expand Down Expand Up @@ -308,9 +269,32 @@ class Unauthorized < ClientError; end
class Forbidden < ClientError; end
class DoesNotExist < ClientError; end

class HashTokenStore < Google::Auth::TokenStore
def initialize(tokens)
@store = tokens.with_indifferent_access
end

def load(id)
@store[id]
end

def store(id, token)
@store[id] = token
end

def delete(id)
@store.delete(id)
end

def to_h
@store
end
end

private

def handle_errors(result)
puts result
return if result.success?
# die if there's an error
if result.response.status >= 500
Expand Down

0 comments on commit 3623fb0

Please sign in to comment.