Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
781a7af
Create wrapper methods for oauth flow to prevent users directly acces…
murraysum Sep 1, 2012
399575d
Fixed invoice view filters
simonmoxon May 15, 2013
27b3203
Added billing_email field to contact
simonmoxon Jul 23, 2013
cda4c12
Made resource.save work like resource.create (return the object and t…
johnknott Jul 24, 2013
d1efae6
Fixed class method reference
johnknott Jul 24, 2013
1cd7e2c
Fixed class method reference
johnknott Jul 24, 2013
faf65ad
fa resource fix
johnknott Aug 1, 2013
afe94a4
fix
johnknott Aug 1, 2013
8a778fb
Clean up some RSpec deprecations
JoeStanton Aug 20, 2013
e1b0844
Added methods to follow REST href's in Timeslip.
JoeStanton Aug 21, 2013
57dfbbf
Merge remote-tracking branch 'upstream/improve-oauth-flow'
JoeStanton Aug 27, 2013
050d83a
Accept a refresh_token and delegate refresh! calls to OAuth2 lib
JoeStanton Aug 27, 2013
439d9c6
Allow initialization from a refresh_token or an access_token
JoeStanton Aug 27, 2013
721397d
Update dependencies
JoeStanton Aug 28, 2013
e31cc6d
Remove Gemfile.lock
JoeStanton Aug 28, 2013
81ec69a
Ignore Gemfile.lock and specify newer OAuth2
JoeStanton Aug 28, 2013
a6123f6
Added send method to invoice class
simonmoxon Aug 31, 2013
05e0012
Changed send method to send_email
simonmoxon Aug 31, 2013
d856e9e
Implemented automatic pagination for GET's
JoeStanton Oct 31, 2013
4e58edb
Fixed bug when passing access_token
JoeStanton Oct 31, 2013
280f2b6
Merge branch 'auto-pagination'
JoeStanton Oct 31, 2013
c2130b0
Merge params before initial request
JoeStanton Oct 31, 2013
d5eef55
Merge branch 'auto-pagination'
JoeStanton Oct 31, 2013
51739fc
Merge remote-tracking branch 'redbadger/master'
simonmoxon Nov 13, 2013
096695a
Gone back to old version of Oauth2 for dependancy reasons
simonmoxon Nov 13, 2013
3938f3c
oAuth change
simonmoxon Nov 13, 2013
c37f5c1
Added paid_on to invoices so we can obtain invoice paid date
smnmxn Jan 9, 2015
ecb2501
Added place_of_supply for VAT Moss
smnmxn Apr 18, 2016
105d98b
Added delete_invoice method to invoices
smnmxn Sep 5, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.rvmrc
example.rb
resource_list

Gemfile.lock
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ source "http://rubygems.org"

# Specify gem dependencies in freeagent-api-ruby.gemspec
gemspec


45 changes: 0 additions & 45 deletions Gemfile.lock

This file was deleted.

2 changes: 1 addition & 1 deletion freeagent-api-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end
end
6 changes: 5 additions & 1 deletion lib/freeagent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ class << self
attr_accessor :environment
attr_accessor :debug
attr_reader :client

def access_details(client_id, client_secret, access_token=nil)
@client = Client.new(client_id, client_secret)
@client.access_token = access_token if access_token
end

def authorize(options)
@client.authorize(options)
end
end
end
70 changes: 67 additions & 3 deletions lib/freeagent/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ def self.token_url
def self.connection_opts
{ :headers => { :user_agent => "freeagent-api-rb", :accept => "application/json", :content_type => "application/json" } }
end


def authorize(options)
if options[:redirect_uri]
@client.auth_code.authorize_url(options)
else
raise FreeAgent::ClientError.new('Redirect uri not specified')
end
end

def fetch_access_token(auth_code, options)
if options[:redirect_uri]
@access_token = @client.auth_code.get_token(auth_code, options)
Expand All @@ -58,8 +66,50 @@ def access_token=(token)
@access_token = OAuth2::AccessToken.new(@client, token)
end

def refresh_token
@access_token.try(:refresh_token)
end

def refresh_token=(refresh_token)
@access_token = OAuth2::AccessToken.new(
@client,
nil,
refresh_token: refresh_token
)
@access_token = @access_token.refresh!
end

def get_default(params)
{
auto_paginate: true,
per_page: 100
}.merge params
end

def get(path, params={})
request(:get, "#{Client.site}#{path}", :params => params).parsed
params = get_default(params)
response = request(:get, "#{Client.site}#{path}", :params => params)

if params[:auto_paginate]
auto_paginate(response, params)
else
response.parsed
end
end

def auto_paginate(response, params)
rels = process_rels(response)
items = response.parsed

while rels[:next]
response = request(:get, rels[:next], :params => params)
rels = process_rels(response)
items.merge response.parsed do |_, current, new|
current.concat new
end
end

items
end

def post(path, data={})
Expand All @@ -74,7 +124,21 @@ def delete(path, data={})
request(:delete, "#{Client.site}#{path}", :data => data).parsed
end

private
private

# Finds link relations from 'Link' response header
#
# Returns an array of Relations
# https://github.com/lostisland/sawyer/blob/master/lib/sawyer/response.rb
def process_rels(response)
links = (response.headers["Link"] || "" ).split(', ').map do |link|
href, name = link.match(/<(.*?)>; rel=['"](\w+)["']/).captures
[name.to_sym, href]
end

Hash[*links.flatten]
end


def request(method, path, options = {})
if @access_token
Expand Down
2 changes: 1 addition & 1 deletion lib/freeagent/contact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Contact < Resource

attr_accessor :first_name, :last_name, :contact_name_on_invoice, :country, :locale, :sales_tax_registration_number, :uses_contact_invoice_sequence

attr_accessor :organisation_name, :email, :phone_number
attr_accessor :organisation_name, :email, :billing_email, :phone_number

attr_accessor :address1, :town, :region, :postcode, :address2, :address3, :country

Expand Down
34 changes: 25 additions & 9 deletions lib/freeagent/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ class Invoice < Resource

resource_methods :default

attr_accessor :contact, :reference, :currency, :status, :omit_header, :payment_terms_in_days, :ec_status, :invoice_items
attr_accessor :contact, :reference, :currency, :status, :omit_header, :payment_terms_in_days, :ec_status, :place_of_supply, :invoice_items

attr_accessor :project, :discount_percent, :written_off_date

decimal_accessor :exchange_rate, :net_value, :sales_tax_value

date_accessor :dated_on, :due_on
date_accessor :dated_on, :due_on, :paid_on

# TODO FIXME Need to rename this better
def self.all_with_nested_items
Expand All @@ -22,27 +22,27 @@ def self.recent_open_or_overdue
end

def self.open_or_overdue
Invoice.filter(:view => 'recent_open_or_overdue')
Invoice.filter(:view => 'open_or_overdue')
end

def self.draft
Invoice.filter(:view => 'recent_open_or_overdue')
Invoice.filter(:view => 'draft')
end

def self.scheduled_to_email
Invoice.filter(:view => 'recent_open_or_overdue')
Invoice.filter(:view => 'scheduled_to_email')
end

def self.thank_you_emails
Invoice.filter(:view => 'recent_open_or_overdue')
Invoice.filter(:view => 'thank_you_emails')
end

def self.reminder_emails
Invoice.filter(:view => 'recent_open_or_overdue')
Invoice.filter(:view => 'reminder_emails')
end

def self.last_month(n)
Invoice.filter(:view => 'recent_open_or_overdue')
Invoice.filter(:view => "last_#{n}_months")
end

def self.find_all_by_contact(contact)
Expand All @@ -58,6 +58,18 @@ def self.find_all_by_project(project)
# FreeAgent.client.post("invoices/#{id}/send_email", email)
#end

def send_email(from, to, subject, msg)
FreeAgent.client.post("invoices/#{id}/send_email",{
:invoice => {
:email => {
:to => to,
:from => from,
:subject => subject,
:body => msg
}
}})
end

def mark_as_sent
FreeAgent.client.put("invoices/#{id}/transitions/mark_as_sent", nil)
end
Expand All @@ -69,10 +81,14 @@ def mark_as_draft
def mark_as_cancelled
FreeAgent.client.put("invoices/#{id}/transitions/mark_as_cancelled", nil)
end

def delete_invoice
FreeAgent.client.delete("invoices/#{id}")
end

# TODO Write invoice timeline wrapper
#def timeline
#
#end
end
end
end
33 changes: 16 additions & 17 deletions lib/freeagent/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ def inspect
vars = to_hash.collect { |k,v| "#{k}=#{v.inspect}" }
"#<#{self.class}: #{vars.join(', ')}>"
end

def to_hash
hash = {}
instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
hash
end

def to_json
MultiJson.encode(to_hash)
end

class << self
attr_accessor :endpoint
end
Expand All @@ -64,7 +64,7 @@ def self.resource_methods(*args)
define_delete if args.include? :delete
end
end

def self.decimal_accessor(*args)
decimal_reader(*args)
decimal_writer(*args)
Expand Down Expand Up @@ -123,7 +123,7 @@ def self.define_find
response = FreeAgent.client.get("#{endpoint[:plural]}/#{id}")
self.new(response[endpoint[:single]])
rescue FreeAgent::ApiError => error
raise error if FreeAgent.debug
raise error if FreeAgent.debug
nil
end
end
Expand All @@ -136,19 +136,18 @@ def self.define_create_and_save
self.new(response[endpoint[:single]])
end

define_method(:save) do
begin
data = { self.class.endpoint[:single].to_sym => self.to_hash }
if persisted?
FreeAgent.client.put("#{self.class.endpoint[:plural]}/#{id}", data)
else
FreeAgent.client.post(self.class.endpoint[:plural], data)
end
true
rescue FreeAgent::ApiError => error
false
end
define_method(:save) do
data = { self.class.endpoint[:single].to_sym => self.to_hash }
response = nil
if persisted?
response = FreeAgent.client.put("#{self.class.endpoint[:plural]}/#{id}", data)
response = FreeAgent.client.get("#{self.class.endpoint[:plural]}/#{id}")
else
response = FreeAgent.client.post(self.class.endpoint[:plural], data)
end
self.class.new(response[self.class.endpoint[:single]])
end

end

def self.define_update
Expand Down
14 changes: 13 additions & 1 deletion lib/freeagent/timeslip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@ class Timeslip < Resource

attr_accessor :user, :project, :task, :comment

def follow_user
FreeAgent::User.find(extract_id(@user))
end

def follow_project
FreeAgent::Project.find(extract_id(@project))
end

def follow_task
FreeAgent::Task.find(extract_id(@task))
end

decimal_accessor :hours
date_accessor :dated_on, :created_at, :updated_at
end
end
end
8 changes: 4 additions & 4 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
end

it 'should raise client error when no client id or secret specified' do
expect { FreeAgent::Client.new(nil, '') }.should raise_error(FreeAgent::ClientError)
expect { FreeAgent::Client.new('', nil) }.should raise_error(FreeAgent::ClientError)
expect { FreeAgent::Client.new(nil, nil) }.should raise_error(FreeAgent::ClientError)
expect { FreeAgent::Client.new(nil, '') }.to raise_error(FreeAgent::ClientError)
expect { FreeAgent::Client.new('', nil) }.to raise_error(FreeAgent::ClientError)
expect { FreeAgent::Client.new(nil, nil) }.to raise_error(FreeAgent::ClientError)
end
end

Expand Down Expand Up @@ -114,7 +114,7 @@

it 'should raise client error when redirect not specified' do
fetch_access_token = expect {@client.fetch_access_token('auth_code', {})}
fetch_access_token.should raise_error(FreeAgent::ClientError)
fetch_access_token.to raise_error(FreeAgent::ClientError)
end
end

Expand Down