Closed Aupajo closed 2 years ago
I like the flexibility of the first approach, this would be useful not only for your purposes but maybe for some other stuff, such as something totally unrelated to Dropbox like a proxy or whatever.
However, after reading a bit of the Business Endpoints documentation, looks like you only need two options: "select user" & "select admin". Having this in mind, it seems like the second proposal is much simpler, specially from the point of view of the library user.
Really, in order to make this decision I would need to know the use cases of whoever is going to be using this. Since you're the first one to get there and I don't have a strong opinion, I think you should go for your preferred solution.
@Jesus Thanks for taking the time to respond!
I'm tempted to implement the first option for now, and see where that gets me. The first and second solutions will work side-by-side, and implementing the first would give me a chance to experiment with the Business API and see how viable the second approach is (which seems like the better long-term approach) as well as opening the door to other use-cases (logging, proxying, etc.).
@Jesus @Aupajo I'm in a similar need, need to access files under Dropbox Business account team folder by passing header --header 'Dropbox-API-Path-Root: {".tag": "namespace_id", "namespace_id": "10"}'
, official documentation here
I tried following with no luck:
client = DropboxApi::Client.new(...)
client.middleware do |connection|
connection.headers['Dropbox-API-Path-Root'] = {".tag": "namespace_id", "namespace_id": "10"}
end
client.list_folder('/gifs')
Any idea how this can be achieved?
@sumonmg Did you figure out how to do it? I am trying to do the same.
@Nerian No, it was painful to resolve and also I find these gems are always behind or something endpoints missing, so I decided to write a simple API client which allows me to add new endpoint very easily whenever I need.
Ugh I'm stuck on this too‥ seems like I shouldn't have bothered with the gem :-/
@sumonmg do you have the full auth flow and file / revision access built for that ? Am about to re-implement the same but would love a working shortcut if you've already built it
@smenor Yes I have a nice working structure, let me give you those here:
app/services/path_dbx/client.rb
:# frozen_string_literal: true
# Path Dropbox Client to work with Dropbox Business account
module PathDbx
class Client
include HTTParty
base_uri 'https://api.dropboxapi.com'
include Endpoints
def initialize(oauth_bearer = ENV['DROPBOX_OAUTH_BEARER'],
namespace_id = ENV['DROPBOX_ROOT_NAMESPACE_ID'],
admin_user_id = ENV['DROPBOX_ADMIN_USER_ID'])
@oauth_bearer = oauth_bearer
@namespace_id = namespace_id
@admin_user_id = admin_user_id
end
private
def headers
# When using business account for production and regular account for staging
if Rails.env.production?
common_headers.merge(business_headers)
else
common_headers
end
end
def headers_for_content
# When using business account for production and regular account for staging
if Rails.env.production?
common_headers_for_content.merge(business_headers)
else
common_headers_for_content
end
end
def common_headers
@common_headers ||= { 'Authorization': "Bearer #{@oauth_bearer}",
'Content-Type': 'application/json' }
end
def common_headers_for_content
@common_headers_for_content ||= { 'Authorization': "Bearer #{@oauth_bearer}" }
end
def business_headers
{ 'Dropbox-API-Select-User': @admin_user_id,
'Dropbox-API-Path-Root': root_path.to_json }
end
def root_path
{ '.tag': 'namespace_id',
'namespace_id': @namespace_id }
end
def handle_response(response)
code = response.code
return response if code == 200
raise(StandardError, response)
end
end
end
app/services/path_dbx/endpoints.rb
:# frozen_string_literal: true
module PathDbx
module Endpoints
CONTENT_URI = 'https://content.dropboxapi.com'
def get_metadata(path, options = {})
params = { path: path }.merge(options)
send_request(:post, '/2/files/get_metadata', params)
end
def add_folder_member(shared_folder_id, emails)
params = { shared_folder_id: shared_folder_id }.merge(members(emails))
send_request(:post, '/2/sharing/add_folder_member', params)
end
def share_folder(path, options = {})
params = { path: path }.merge(options)
send_request(:post, '/2/sharing/share_folder', params)
end
def check_share_job_status(async_job_id)
params = { async_job_id: async_job_id }
send_request(:post, '/2/sharing/check_share_job_status', params)
end
def create_folder(path, options = {})
params = { path: path }.merge(options)
send_request(:post, '/2/files/create_folder_v2', params)
end
def delete(path)
params = { path: path }
send_request(:post, '/2/files/delete_v2', params)
end
def permanently_delete(path)
params = { path: path }
send_request(:post, '/2/files/permanently_delete', params)
end
def list_folder(path, options = {})
params = { path: path }.merge(options)
send_request(:post, '/2/files/list_folder', params)
end
def list_folder_continue(cursor)
params = { cursor: cursor }
send_request(:post, '/2/files/list_folder/continue', params)
end
def search(query, options = {})
params = { query: query, options: options }
send_request(:post, '/2/files/search_v2', params)
end
def move(from_path, to_path, options = {})
params = { from_path: from_path, to_path: to_path }.merge(options)
send_request(:post, '/2/files/move_v2', params)
end
def get_thumbnail(path, options = {})
params = { resource: { '.tag': 'path', path: path } }.merge(options)
send_request_for_content(:get, '/2/files/get_thumbnail_v2', params)
end
def get_temporary_link(path)
params = { path: path }
send_request(:post, '/2/files/get_temporary_link', params)
end
private
def send_request(action, path, params)
response = self.class.send(action,
path,
body: params.to_json,
headers: headers)
handle_response(response)
end
def send_request_for_content(action, path, params)
headers = headers_for_content.merge(content_headers(params))
response = HTTParty.send(action,
CONTENT_URI + path,
headers: headers)
handle_response(response)
end
def content_headers(params)
{ 'Dropbox-API-Arg': params.to_json }
end
def members(emails)
member_params = []
emails.each do |email|
member = { '.tag': 'email', 'email': email }
member_params << { member: member,
access_level: 'editor' }
end
{ members: member_params }
end
end
end
dropbox = PathDbx::Client.new
dropbox.create_folder(path)
Thanks / much appreciated @sumonmg !
Oops, seems like we need to improve the docs. Achieving that should be quite simple, take your example:
client = DropboxApi::Client.new(...)
client.middleware do |connection|
connection.headers['Dropbox-API-Path-Root'] = {".tag": "namespace_id", "namespace_id": "10"}
end
client.list_folder('/gifs')
Would need to be rewritten as:
client = DropboxApi::Client.new(...)
client.namespace_id = 10
client.list_folder('/gifs')
I'll leave this open for a few days in case someone has further questions.
I've written an app that uses the
dropbox_api
gem to list and upload files. It works great, but now I need to make it work with the Dropbox Business API.The Dropbox Business API has a set if its own endpoints, plus a change in request behaviour for the normal Dropbox API endpoint. At this stage, I don't need the additional endpoints, just the altered request behaviour.
The change needed is to add a header –
Dropbox-API-Select-User
(orDropbox-API-Select-Admin
) – which contains a team member ID (this is separate from a user ID). Using this header, I can request any of the standard API endpoints, performing each action on behalf of a user in a team.There's a couple of approaches I can take to implementing this, and ideally I'd like to submit the work and get it accepted back to this gem, so I'm opening this issue for discussion now before I start work on it. I'd love to hear your thoughts.
Proposed solution: Expose ConnectionBuilder's connection middleware
One option would be to add a method to the client which would allow you to have access to the
ConnectionBuilder
's Faraday connection:This would be my preferred solution, as exposing the middleware would allow a user to arbitrarily add, for example, custom logging or error handling.
Given that some endpoints manipulate the headers, this may cause some headaches. There's also the classic problem of Faraday's middleware ordering – the ability to customise the connection will depend on where the callback gets called inside the block.
Alternative option: Pass parameter to ConnectionBuilder
Another option to solve this problem would be to pass an optional parameter to the connection builder, through the client:
This would work by passing the
select_user
to theConnectionBuilder
, which would insert it something like this:Alternative option: support a parameter on each endpoint
This would basically involving patching a bunch of endpoints to allow a
select_user
option.