Last active
July 20, 2016 09:55
-
-
Save isaacabraham/aa3e9a73f503e7855aa7 to your computer and use it in GitHub Desktop.
FreeAgent file download
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#I @"packages" | |
#r @"Http.Fs\lib\net40\HttpClient.dll" | |
#r @"FSharp.Data\lib\net40\FSharp.Data.dll" | |
open HttpClient | |
open FSharp.Data | |
module Security = | |
type RefreshTokenResponse = JsonProvider< """{ | |
"access_token":"ACCESS TOKEN", | |
"token_type":"bearer", | |
"expires_in":604800 | |
}"""> | |
let private contactTokenServer body = | |
"https://api.freeagent.com/v2/token_endpoint" | |
|> createRequest Post | |
|> withBasicAuthentication clientId secret | |
|> withHeader (ContentType "application/x-www-form-urlencoded") | |
|> withHeader (RequestHeader.AcceptCharset "UTF-8") | |
|> withBody body | |
|> getResponseBody | |
let private refreshToken = "REFRESH TOKEN GOES HERE" | |
let private getFreshToken = | |
sprintf "grant_type=refresh_token&refresh_token=%s" | |
>> contactTokenServer | |
>> RefreshTokenResponse.Parse | |
let accessToken = getFreshToken refreshToken | |
module Communication = | |
let private withFreeAgentAuth = | |
sprintf "Bearer %s" | |
>> Authorization | |
>> withHeader | |
let private withAuth = withFreeAgentAuth Security.accessToken.AccessToken | |
let withStandardHeaders = | |
withAuth | |
>> withHeader (Accept "application/json") | |
>> withHeader (ContentType "application/json") | |
>> withHeader (UserAgent "fsharp interactive") | |
let makeStandardRequest parser = | |
createRequest Get | |
>> withStandardHeaders | |
>> getResponseBody | |
>> parser | |
let private getNextPageUri = | |
let splitOn (text:string) (item:string) = item.Split([|text|], System.StringSplitOptions.RemoveEmptyEntries) | |
fun (reponse:Response) -> | |
reponse.Headers.[ResponseHeader.Link] |> splitOn ", " | |
|> Seq.map (fun item -> | |
let [|uri;direction|] = item |> splitOn "; " | |
uri, (direction |> splitOn "=").[1].Trim '\'') | |
|> Seq.tryFind (snd >> (=) "next") | |
|> Option.map fst | |
|> Option.map(fun s -> s.Trim('<', '>')) | |
let createPagedRequest parser getCollection uri = | |
let rec getNextPage uri = | |
seq { | |
let response = | |
uri | |
|> createRequest Get | |
|> withStandardHeaders | |
|> getResponse | |
match response.EntityBody with | |
| Some body -> | |
let collection = body |> parser |> getCollection | |
yield! collection | |
let nextUri = getNextPageUri response | |
match nextUri with | |
| Some nextUri -> yield! getNextPage nextUri | |
| None -> () | |
| None -> () | |
} | |
getNextPage (sprintf "%s%spage=1&per_page=100" uri (if uri.Contains "?" then "&" else "?")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[<AutoOpen>] | |
module FreeAgent = | |
type GetProjectsResponse = JsonProvider< """{ "projects":[ | |
{ | |
"url":"https://api.freeagent.com/v2/projects/1", | |
"name":"Test Project", | |
"contact":"https://api.freeagent.com/v2/contacts/1", | |
"budget":0, | |
"is_ir35":false, | |
"status":"Active", | |
"budget_units":"Hours", | |
"normal_billing_rate":"0.0", | |
"hours_per_day":"8.0", | |
"uses_project_invoice_sequence":false, | |
"currency":"GBP", | |
"billing_period":"hour", | |
"created_at":"2011-09-14T16:05:57Z", | |
"updated_at":"2011-09-14T16:05:57Z" | |
} | |
]}"""> | |
type GetCustomersResponse = JsonProvider< """{ "contacts":[ | |
{ | |
"organisation_name":"foo", | |
"url":"https://api.freeagent.com/v2/contacts/2", | |
"first_name":"test", | |
"last_name":"me", | |
"contact_name_on_invoices":true, | |
"country":"United Kingdom", | |
"locale":"en", | |
"account_balance":"-100.0", | |
"uses_contact_invoice_sequence":false, | |
"created_at":"2011-09-14T16:00:41Z", | |
"updated_at":"2011-09-16T09:34:41Z" | |
} | |
]}"""> | |
type GetTimeslipsResponse = JsonProvider< """{ "timeslips":[ | |
{ | |
"url":"https://api.freeagent.com/v2/timeslips/25", | |
"user":"https://api.freeagent.com/v2/users/1", | |
"project":"https://api.freeagent.com/v2/projects/1", | |
"task":"https://api.freeagent.com/v2/tasks/1", | |
"dated_on":"2011-08-15", | |
"hours":"12.0", | |
"updated_at":"2011-08-16T13:32:00Z", | |
"created_at":"2011-08-16T13:32:00Z" | |
} | |
]}"""> | |
type GetInvoicesResponse = JsonProvider< """{ "invoices": [ { | |
"url":"https://api.freeagent.com/v2/invoices/1", | |
"contact":"https://api.freeagent.com/v2/contacts/2", | |
"dated_on":"2011-08-29T00:00:00+00:00", | |
"due_on":"2011-09-28T00:00:00+00:00", | |
"reference":"001", | |
"currency":"GBP", | |
"exchange_rate":"1.0", | |
"net_value":"0.0", | |
"sales_tax_value":"0.0", | |
"total_value": "200.0", | |
"paid_value": "50.0", | |
"due_value": "150.0", | |
"status":"Draft", | |
"comments":"An example invoice comment.", | |
"omit_header":false, | |
"payment_terms_in_days":30, | |
"ec_status":"EC Goods", | |
"created_at":"2011-08-29T00:00:00Z", | |
"updated_at":"2011-08-29T00:00:00Z" | |
} | |
]}"""> | |
type GetExpensesResponse = JsonProvider< """{ "expenses":[ | |
{ | |
"url":"https://api.freeagent.com/v2/expenses/1", | |
"user":"https://api.freeagent.com/v2/users/1", | |
"category":"https://api.freeagent.com/v2/categories/285", | |
"dated_on":"2011-08-24", | |
"currency":"USD", | |
"gross_value":"-20.0", | |
"native_gross_value":"-12.0", | |
"sales_tax_rate":"1.0", | |
"sales_tax_value": "-0.2", | |
"native_sales_tax_value": "-0.12", | |
"description":"Some description", | |
"manual_sales_tax_amount":"0.12", | |
"updated_at":"2011-08-24T08:10:40Z", | |
"created_at":"2011-08-24T08:10:40Z", | |
"attachment": | |
{ | |
"url":"https://api.freeagent.com/v2/attachments/3", | |
"content_src":"https://s3.amazonaws.com/freeagent-dev/attachments/1/original.png?AWSAccessKeyId=1234", | |
"content_type":"image/png", | |
"file_name":"barcode.png", | |
"file_size":7673 | |
} | |
}, | |
{ | |
"url":"https://api.freeagent.com/v2/expenses/1", | |
"user":"https://api.freeagent.com/v2/users/1", | |
"category":"https://api.freeagent.com/v2/categories/285", | |
"dated_on":"2011-08-24", | |
"currency":"USD", | |
"gross_value":"-20.0", | |
"native_gross_value":"-12.0", | |
"sales_tax_rate":"1.0", | |
"sales_tax_value": "-0.2", | |
"native_sales_tax_value": "-0.12", | |
"description":"Some description", | |
"manual_sales_tax_amount":"0.12", | |
"updated_at":"2011-08-24T08:10:40Z", | |
"created_at":"2011-08-24T08:10:40Z" | |
} | |
]}"""> | |
type GetBillsResponse = JsonProvider< """{ "bills":[{ | |
"url":"https://api.freeagent.com/v2/bills/1", | |
"contact":"https://api.freeagent.com/v2/contacts/1", | |
"category":"https://api.freeagent.com/v2/categories/285", | |
"reference":"acsad", | |
"dated_on":"2011-07-28", | |
"due_on":"2011-08-27", | |
"total_value":"213.0", | |
"paid_value":"200.0", | |
"due_value":"13.0", | |
"sales_tax_value":"-35.5", | |
"sales_tax_rate":"20.0", | |
"status":"Open", | |
"updated_at":"2011-07-28T12:43:36Z", | |
"created_at":"2011-07-28T12:43:36Z", | |
"attachment": | |
{ | |
"url":"https://api.freeagent.com/v2/attachments/3", | |
"content_src":"https://s3.amazonaws.com/freeagent-dev/attachments/1/original.png?AWSAccessKeyId=1234", | |
"content_type":"image/png", | |
"file_name":"barcode.png", | |
"file_size":7673 | |
} | |
}, | |
{ | |
"url":"https://api.freeagent.com/v2/bills/1", | |
"contact":"https://api.freeagent.com/v2/contacts/1", | |
"category":"https://api.freeagent.com/v2/categories/285", | |
"reference":"acsad", | |
"dated_on":"2011-07-28", | |
"due_on":"2011-08-27", | |
"total_value":"213.0", | |
"paid_value":"200.0", | |
"due_value":"13.0", | |
"sales_tax_value":"-35.5", | |
"sales_tax_rate":"20.0", | |
"status":"Open", | |
"updated_at":"2011-07-28T12:43:36Z", | |
"created_at":"2011-07-28T12:43:36Z" | |
} | |
]}"""> | |
type GetBankAccountsResponse = JsonProvider< """{ "bank_accounts":[ | |
{ | |
"url":"https://api.freeagent.com/v2/bank_accounts/1", | |
"opening_balance":"0.0", | |
"type":"StandardBankAccount", | |
"name":"Default bank account", | |
"is_personal":false, | |
"currency": "GBP", | |
"current_balance": "0.0", | |
"updated_at":"2011-07-28T11:25:20Z", | |
"created_at":"2011-07-28T11:25:11Z" | |
} | |
]}"""> | |
type GetBankTxnExplanationResponse = JsonProvider< """{ "bank_transaction_explanations": [ | |
{ | |
"url": "https://api.freeagent.com/v2/bank_transaction_explanations/20", | |
"bank_transaction": "https://api.freeagent.com/v2/bank_transactions/20", | |
"bank_account": "https://api.freeagent.com/v2/bank_accounts/1", | |
"category": "https://api.freeagent.com/v2/categories/366", | |
"dated_on": "2010-12-01", | |
"description": "transform plug-and-play convergence", | |
"gross_value": "-90.0", | |
"attachment": | |
{ | |
"url":"https://api.freeagent.com/v2/attachments/3", | |
"content_src":"https://s3.amazonaws.com/freeagent-dev/attachments/2/original.pdf?AWSAccessKeyId=1234", | |
"content_type":"application/pdf", | |
"file_name":"About Stacks.pdf", | |
"file_size":466028 | |
} | |
}, | |
{ | |
"url": "https://api.freeagent.com/v2/bank_transaction_explanations/20", | |
"bank_transaction": "https://api.freeagent.com/v2/bank_transactions/20", | |
"bank_account": "https://api.freeagent.com/v2/bank_accounts/1", | |
"category": "https://api.freeagent.com/v2/categories/366", | |
"dated_on": "2010-12-01", | |
"description": "transform plug-and-play convergence", | |
"gross_value": "-90.0" | |
}] | |
}"""> | |
let buildUri = sprintf "https://api.freeagent.com/v2/%s" | |
let Projects = | |
"projects" | |
|> buildUri | |
|> Communication.createPagedRequest | |
GetProjectsResponse.Parse | |
(fun p -> p.Projects) | |
|> Seq.cache | |
let Customers = | |
"contacts" | |
|> buildUri | |
|> Communication.createPagedRequest | |
GetCustomersResponse.Parse | |
(fun t -> t.Contacts) | |
|> Seq.map(fun x -> x.Url, x) | |
|> Map.ofSeq | |
let getTimeslips = | |
sprintf "timeslips?project=%s" | |
>> buildUri | |
>> Communication.createPagedRequest | |
GetTimeslipsResponse.Parse | |
(fun t -> t.Timeslips) | |
let getInvoices = | |
sprintf "invoices?project=%s" | |
>> buildUri | |
>> Communication.createPagedRequest | |
GetInvoicesResponse.Parse | |
(fun t -> t.Invoices) | |
let getExpenses() = | |
"expenses" | |
|> buildUri | |
|> Communication.createPagedRequest | |
GetExpensesResponse.Parse | |
(fun t -> t.Expenses) | |
let getBills() = | |
"bills" | |
|> buildUri | |
|> Communication.createPagedRequest | |
GetBillsResponse.Parse | |
(fun t -> t.Bills) | |
let getBankAccounts() = | |
"bank_accounts" | |
|> buildUri | |
|> Communication.createPagedRequest | |
GetBankAccountsResponse.Parse | |
(fun t -> t.BankAccounts) | |
let getBankTxnExplanations = | |
sprintf "bank_transaction_explanations?bank_account=%s" | |
>> buildUri | |
>> Communication.createPagedRequest | |
GetBankTxnExplanationResponse.Parse | |
(fun t -> t.BankTransactionExplanations) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let rootFolder = @"YOUR DOWNLOAD FOLDER" | |
open System | |
open System.IO | |
let whenExists getProp item = match getProp item with Some prop -> Some(item, prop) | None -> None | |
let buildPath rootFolder subFolder (date:System.DateTime) filename = | |
let year = date.Year.ToString("0000") | |
let month = date.Month.ToString("00") | |
let dom = date.Day.ToString("00") | |
Path.Combine(rootFolder, subFolder, year, month, dom, filename) | |
let downloadFile (path, uri) = | |
async { | |
let! contents = | |
uri | |
|> createRequest Get | |
|> getResponseBytesAsync | |
Directory.CreateDirectory(Path.GetDirectoryName(path)) |> ignore | |
use s = File.Create(path) | |
do! s.WriteAsync(contents, 0, contents.Length) |> Async.AwaitTask | |
return path | |
} | |
let bankTxns = | |
getBankAccounts() | |
|> Seq.collect(fun b -> getBankTxnExplanations b.Url) | |
|> Seq.choose(whenExists(fun e -> e.Attachment)) | |
|> Seq.map(fun (e, attachment) -> buildPath rootFolder "BankTxns" e.DatedOn attachment.FileName, attachment.ContentSrc) | |
|> Seq.map downloadFile | |
|> Async.Parallel | |
|> Async.RunSynchronously | |
let bills = | |
getBills() | |
|> Seq.choose(whenExists(fun e -> e.Attachment)) | |
|> Seq.map(fun (e, attachment) -> buildPath rootFolder "bills" e.DatedOn attachment.FileName, attachment.ContentSrc) | |
|> Seq.map downloadFile | |
|> Async.Parallel | |
|> Async.RunSynchronously | |
let expenses = | |
getExpenses() | |
|> Seq.choose(whenExists(fun e -> e.Attachment)) | |
|> Seq.map(fun (e, attachment) -> buildPath rootFolder "expenses" e.DatedOn attachment.FileName, attachment.ContentSrc) | |
|> Seq.map downloadFile | |
|> Async.Parallel | |
|> Async.RunSynchronously |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment