Last active
July 20, 2016 09:55
-
-
Save isaacabraham/aa3e9a73f503e7855aa7 to your computer and use it in GitHub Desktop.
Revisions
-
isaacabraham revised this gist
Nov 26, 2015 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -90,7 +90,7 @@ module FreeAgent = "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 @@ -132,7 +132,7 @@ module FreeAgent = "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 @@ -182,7 +182,7 @@ module FreeAgent = "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 -
isaacabraham revised this gist
Jun 12, 2015 . 1 changed file with 0 additions and 2 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,11 +1,9 @@ #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< """{ -
isaacabraham revised this gist
Jun 12, 2015 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -3,7 +3,7 @@ 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") @@ -25,23 +25,23 @@ let downloadFile (path, uri) = 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 -
isaacabraham revised this gist
Jun 12, 2015 . 3 changed files with 9 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,12 @@ #I @"packages" #r @"Http.Fs\lib\net40\HttpClient.dll" #r @"FSharp.Data\lib\net40\FSharp.Data.dll" #load @"Deedle\Deedle.fsx" open HttpClient open FSharp.Data open Deedle module Security = type RefreshTokenResponse = JsonProvider< """{ "access_token":"ACCESS TOKEN", File renamed without changes.File renamed without changes. -
isaacabraham revised this gist
Jun 12, 2015 . 2 changed files with 309 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,48 @@ let rootFolder = @"YOUR DOWNLOAD FOLDER" open System open System.IO let getItem 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(getItem(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(getItem(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(getItem(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 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,261 @@ [<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=1K3MW21E6T8KWBY84B02&Expires=1314281186&Signature=GFAKDo%2Bi%2FsUMTYEgg6ZWGysB4k4%3D", "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=1K3MW21E6T8KWBY84B02&Expires=1314281186&Signature=GFAKDo%2Bi%2FsUMTYEgg6ZWGysB4k4%3D", "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=1K3MW21E6T8KWBY84B02&Expires=1316186571&Signature=tA4V5%2BJEE%2Fc3JTg5AiIO494m0cA%3D", "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) -
isaacabraham created this gist
Jun 12, 2015 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,75 @@ 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 "?"))