diff options
Diffstat (limited to 'tools/monzo_ynab')
-rw-r--r-- | tools/monzo_ynab/.envrc | 8 | ||||
-rw-r--r-- | tools/monzo_ynab/.gitignore | 3 | ||||
-rw-r--r-- | tools/monzo_ynab/README.md | 41 | ||||
-rw-r--r-- | tools/monzo_ynab/auth.go | 101 | ||||
-rw-r--r-- | tools/monzo_ynab/job.nix | 12 | ||||
-rw-r--r-- | tools/monzo_ynab/main.go | 43 | ||||
-rw-r--r-- | tools/monzo_ynab/monzo/client.go | 52 | ||||
-rw-r--r-- | tools/monzo_ynab/monzo/serde.go | 82 | ||||
-rw-r--r-- | tools/monzo_ynab/requests.txt | 80 | ||||
-rw-r--r-- | tools/monzo_ynab/shell.nix | 10 | ||||
-rw-r--r-- | tools/monzo_ynab/tokens.go | 283 | ||||
-rw-r--r-- | tools/monzo_ynab/tokens.nix | 23 | ||||
-rw-r--r-- | tools/monzo_ynab/ynab/client.go | 24 | ||||
-rw-r--r-- | tools/monzo_ynab/ynab/serde.go | 52 |
14 files changed, 0 insertions, 814 deletions
diff --git a/tools/monzo_ynab/.envrc b/tools/monzo_ynab/.envrc deleted file mode 100644 index f368d0b7e813..000000000000 --- a/tools/monzo_ynab/.envrc +++ /dev/null @@ -1,8 +0,0 @@ -source_up -use_nix -export monzo_client_id="$(jq -j '.monzo | .clientId' < ~/briefcase/secrets.json)" -export monzo_client_secret="$(jq -j '.monzo | .clientSecret' < ~/briefcase/secrets.json)" -export ynab_personal_access_token="$(jq -j '.ynab | .personalAccessToken' < ~/briefcase/secrets.json)" -export ynab_account_id="$(jq -j '.ynab | .accountId' < ~/briefcase/secrets.json)" -export ynab_budget_id="$(jq -j '.ynab | .budgetId' < ~/briefcase/secrets.json)" -export store_path="$(pwd)" diff --git a/tools/monzo_ynab/.gitignore b/tools/monzo_ynab/.gitignore deleted file mode 100644 index e92078303bec..000000000000 --- a/tools/monzo_ynab/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/ynab/fixture.json -/monzo/fixture.json -/kv.json diff --git a/tools/monzo_ynab/README.md b/tools/monzo_ynab/README.md deleted file mode 100644 index 4ccbb35d8c5d..000000000000 --- a/tools/monzo_ynab/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# monzo_ynab - -Exporting Monzo transactions to my YouNeedABudget.com (i.e. YNAB) account. YNAB -unfortunately doesn't currently offer an Monzo integration. As a workaround and -a practical excuse to learn Go, I decided to write one myself. - -This job is going to run N times per 24 hours. Monzo offers webhooks for -reacting to certain types of events. I don't expect I'll need realtime data for -my YNAB integration. That may change, however, so it's worth noting. - -## Installation - -Like many other packages in this repository, `monzo_ynab` is packaged using -Nix. To install and use, you have two options: - -You can install using `nix-build` and then run the resulting -`./result/bin/monzo_ynab`. - -```shell -> nix-build . && ./result/bin/monzo_ynab -``` - -Or you can install using `nix-env` if you'd like to create the `monzo_ynab` -symlink. - -```shell -> nix-env -f ~/briefcase/monzo_ynab -i -``` - -## Deployment - -While this project is currently not deployed, my plan is to host it on Google -Cloud and run it as a Cloud Run application. What I don't yet know is whether or -not this is feasible or a good idea. One complication that I foresee is that the -OAuth 2.0 login flow requires a web browser until the access token and refresh -tokens are acquired. I'm unsure how to workaround this at the moment. - -For more information about the general packaging and deployment strategies I'm -currently using, refer to the [deployments][deploy] writeup. - -[deploy]: ../deploy/README.md diff --git a/tools/monzo_ynab/auth.go b/tools/monzo_ynab/auth.go deleted file mode 100644 index b66bacb10687..000000000000 --- a/tools/monzo_ynab/auth.go +++ /dev/null @@ -1,101 +0,0 @@ -package auth - -//////////////////////////////////////////////////////////////////////////////// -// Dependencies -//////////////////////////////////////////////////////////////////////////////// - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "net/url" - "os" - "os/exec" - "utils" -) - -//////////////////////////////////////////////////////////////////////////////// -// Constants -//////////////////////////////////////////////////////////////////////////////// - -var ( - BROWSER = os.Getenv("BROWSER") - REDIRECT_URI = "http://localhost:8080/authorization-code" -) - -//////////////////////////////////////////////////////////////////////////////// -// Types -//////////////////////////////////////////////////////////////////////////////// - -// This is the response returned from Monzo when we exchange our authorization -// code for an access token. While Monzo returns additional fields, I'm only -// interested in AccessToken and RefreshToken. -type accessTokenResponse struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int `json:"expires_in"` -} - -type Tokens struct { - AccessToken string - RefreshToken string - ExpiresIn int -} - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -// Returns the access token and refresh tokens for the Monzo API. -func GetTokensFromAuthCode(authCode string, clientID string, clientSecret string) *Tokens { - res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{ - "grant_type": {"authorization_code"}, - "client_id": {clientID}, - "client_secret": {clientSecret}, - "redirect_uri": {REDIRECT_URI}, - "code": {authCode}, - }) - utils.FailOn(err) - defer res.Body.Close() - payload := &accessTokenResponse{} - json.NewDecoder(res.Body).Decode(payload) - - return &Tokens{payload.AccessToken, payload.RefreshToken, payload.ExpiresIn} -} - -// Open a web browser to allow the user to authorize this application. Return -// the authorization code sent from Monzo. -func GetAuthCode(clientID string) string { - // TODO(wpcarro): Consider generating a random string for the state when the - // application starts instead of hardcoding it here. - state := "xyz123" - url := fmt.Sprintf( - "https://auth.monzo.com/?client_id=%s&redirect_uri=%s&response_type=code&state=%s", - clientID, REDIRECT_URI, state) - exec.Command(BROWSER, url).Start() - - authCode := make(chan string) - go func() { - log.Fatal(http.ListenAndServe(":8080", - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // 1. Get authorization code from Monzo. - if req.URL.Path == "/authorization-code" { - params := req.URL.Query() - reqState := params["state"][0] - code := params["code"][0] - - if reqState != state { - log.Fatalf("Value for state returned by Monzo does not equal our state. %s != %s", reqState, state) - } - authCode <- code - - fmt.Fprintf(w, "Authorized!") - } else { - log.Printf("Unhandled request: %v\n", *req) - } - }))) - }() - result := <-authCode - return result -} diff --git a/tools/monzo_ynab/job.nix b/tools/monzo_ynab/job.nix deleted file mode 100644 index 1e10751012e2..000000000000 --- a/tools/monzo_ynab/job.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ depot, briefcase, ... }: - -depot.buildGo.program { - name = "job"; - srcs = [ - ./main.go - ]; - deps = with briefcase.gopkgs; [ - kv - utils - ]; -} diff --git a/tools/monzo_ynab/main.go b/tools/monzo_ynab/main.go deleted file mode 100644 index 06f1944eab70..000000000000 --- a/tools/monzo_ynab/main.go +++ /dev/null @@ -1,43 +0,0 @@ -// Exporting Monzo transactions to my YouNeedABudget.com (i.e. YNAB) -// account. YNAB unfortunately doesn't currently offer an Monzo integration. As -// a workaround and a practical excuse to learn Go, I decided to write one -// myself. -// -// This job is going to run N times per 24 hours. Monzo offers webhooks for -// reacting to certain types of events. I don't expect I'll need realtime data -// for my YNAB integration. That may change, however, so it's worth noting. - -package main - -import ( - "fmt" -) - -var ( - ynabAccountID = os.Getenv("ynab_account_id") -) - -//////////////////////////////////////////////////////////////////////////////// -// Business Logic -//////////////////////////////////////////////////////////////////////////////// - -// Convert a Monzo transaction struct, `tx`, into a YNAB transaction struct. -func toYnab(tx monzoSerde.Transaction) ynabSerde.Transaction { - return ynabSerde.Transaction{ - Id: tx.Id, - Date: tx.Created, - Amount: tx.Amount, - Memo: tx.Notes, - AccountId: ynabAccountID, - } -} - -func main() { - txs := monzo.TransactionsLast24Hours() - var ynabTxs []ynabSerde.Transaction{} - for tx := range txs { - append(ynabTxs, toYnab(tx)) - } - ynab.PostTransactions(ynabTxs) - os.Exit(0) -} diff --git a/tools/monzo_ynab/monzo/client.go b/tools/monzo_ynab/monzo/client.go deleted file mode 100644 index 8c6c41e29f40..000000000000 --- a/tools/monzo_ynab/monzo/client.go +++ /dev/null @@ -1,52 +0,0 @@ -package monzoClient - -import ( - "fmt" - "log" - "monzoSerde" - "net/http" - "net/url" - "strings" - "time" - "tokens" - "utils" -) - -const ( - accountID = "pizza" -) - -type Client struct{} - -// Ensure that the token server is running and return a new instance of a Client -// struct. -func Create() *Client { - tokens.StartServer() - time.Sleep(time.Second * 1) - return &Client{} -} - -// Returns a slice of transactions from the last 24 hours. -func (c *Client) Transactions24Hours() []monzoSerde.Transaction { - token := tokens.AccessToken() - form := url.Values{"account_id": {accountID}} - client := http.Client{} - req, _ := http.NewRequest("POST", "https://api.monzo.com/transactions", - strings.NewReader(form.Encode())) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - req.Header.Add("User-Agent", "monzo-ynab") - res, err := client.Do(req) - - utils.DebugRequest(req) - utils.DebugResponse(res) - - if err != nil { - utils.DebugRequest(req) - utils.DebugResponse(res) - log.Fatal(err) - } - defer res.Body.Close() - - return []monzoSerde.Transaction{} -} diff --git a/tools/monzo_ynab/monzo/serde.go b/tools/monzo_ynab/monzo/serde.go deleted file mode 100644 index a38585eca632..000000000000 --- a/tools/monzo_ynab/monzo/serde.go +++ /dev/null @@ -1,82 +0,0 @@ -// This package hosts the serialization and deserialization logic for all of the -// data types with which our application interacts from the Monzo API. -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "time" -) - -type TxMetadata struct { - FasterPayment string `json:"faster_payment"` - FpsPaymentId string `json:"fps_payment_id"` - Insertion string `json:"insertion"` - Notes string `json:"notes"` - Trn string `json:"trn"` -} - -type TxCounterparty struct { - AccountNumber string `json:"account_number"` - Name string `json:"name"` - SortCode string `json:"sort_code"` - UserId string `json:"user_id"` -} - -type Transaction struct { - Id string `json:"id"` - Created time.Time `json:"created"` - Description string `json:"description"` - Amount int `json:"amount"` - Currency string `json:"currency"` - Notes string `json:"notes"` - Metadata TxMetadata - AccountBalance int `json:"account_balance"` - International interface{} `json:"international"` - Category string `json:"category"` - IsLoad bool `json:"is_load"` - Settled time.Time `json:"settled"` - LocalAmount int `json:"local_amount"` - LocalCurrency string `json:"local_currency"` - Updated time.Time `json:"updated"` - AccountId string `json:"account_id"` - UserId string `json:"user_id"` - Counterparty TxCounterparty `json:"counterparty"` - Scheme string `json:"scheme"` - DedupeId string `json:"dedupe_id"` - Originator bool `json:"originator"` - IncludeInSpending bool `json:"include_in_spending"` - CanBeExcludedFromBreakdown bool `json:"can_be_excluded_from_breakdown"` - CanBeMadeSubscription bool `json:"can_be_made_subscription"` - CanSplitTheBill bool `json:"can_split_the_bill"` - CanAddToTab bool `json:"can_add_to_tab"` - AmountIsPending bool `json:"amount_is_pending"` - // Fees interface{} `json:"fees"` - // Merchant interface `json:"merchant"` - // Labels interface{} `json:"labels"` - // Attachments interface{} `json:"attachments"` - // Categories interface{} `json:"categories"` -} - -// Attempts to encode a Monzo transaction struct into a string. -func serializeTx(tx *Transaction) (string, error) { - x, err := json.Marshal(tx) - return string(x), err -} - -// Attempts to parse a string encoding a transaction presumably sent from a -// Monzo server. -func deserializeTx(x string) (*Transaction, error) { - target := &Transaction{} - err := json.Unmarshal([]byte(x), target) - return target, err -} - -func main() { - b, _ := ioutil.ReadFile("./fixture.json") - tx := string(b) - target, _ := deserializeTx(tx) - out, _ := serializeTx(target) - fmt.Println(out) -} diff --git a/tools/monzo_ynab/requests.txt b/tools/monzo_ynab/requests.txt deleted file mode 100644 index 2da17c0b326a..000000000000 --- a/tools/monzo_ynab/requests.txt +++ /dev/null @@ -1,80 +0,0 @@ -################################################################################ -# YNAB -################################################################################ -:ynab = https://api.youneedabudget.com/v1 -:ynab-access-token := (getenv "ynab_personal_access_token") -:ynab-budget-id := (getenv "ynab_budget_id") -:ynab-account-id := (getenv "ynab_account_id") - -# Test -GET :ynab/budgets -Authorization: Bearer :ynab-access-token - -# List transactions -GET :ynab/budgets/:ynab-budget-id/transactions -Authorization: Bearer :ynab-access-token - -# Post transactions -POST :ynab/budgets/:ynab-budget-id/transactions -Authorization: Bearer :ynab-access-token -Content-Type: application/json -{ - "transactions": [ - { - "account_id": ":ynab-account-id", - "date": "2019-12-30", - "amount": 10000, - "payee_name": "Richard Stallman", - "memo": "Not so free software after all...", - "cleared": "cleared", - "approved": true, - "flag_color": "red", - "import_id": "xyz-123" - } - ] -} - -################################################################################ -# Monzo -################################################################################ -:monzo = https://api.monzo.com -:monzo-access-token := (getenv "monzo_cached_access_token") -:monzo-refresh-token := (getenv "monzo_cached_refresh_token") -:monzo-client-id := (getenv "monzo_client_id") -:monzo-client-secret := (getenv "monzo_client_secret") -:monzo-account-id := (getenv "monzo_account_id") - -# List transactions -GET :monzo/transactions -Authorization: Bearer :monzo-access-token -account_id==:monzo-account-id - -# Refresh access token -# According from the docs, the access token expires in 6 hours. -POST :monzo/oauth2/token -Content-Type: application/x-www-form-urlencoded -Authorization: Bearer :monzo-access-token -grant_type=refresh_token&client_id=:monzo-client-id&client_secret=:monzo-client-secret&refresh_token=:monzo-refresh-token - -################################################################################ -# Tokens server -################################################################################ -:tokens = http://localhost:4242 - -# Get tokens -GET :tokens/tokens - -# Get application state for debugging purposes -GET :tokens/state - -# Force refresh tokens -POST :tokens/refresh-tokens - -# Set tokens -POST :tokens/set-tokens -Content-Type: application/json -{ - "access_token": "access-token", - "refresh_token": "refresh-token", - "expires_in": 120 -} diff --git a/tools/monzo_ynab/shell.nix b/tools/monzo_ynab/shell.nix deleted file mode 100644 index 910d7c1829e2..000000000000 --- a/tools/monzo_ynab/shell.nix +++ /dev/null @@ -1,10 +0,0 @@ -let - briefcase = import <briefcase> {}; - pkgs = briefcase.third_party.pkgs; -in pkgs.mkShell { - buildInputs = [ - pkgs.go - pkgs.goimports - pkgs.godef - ]; -} diff --git a/tools/monzo_ynab/tokens.go b/tools/monzo_ynab/tokens.go deleted file mode 100644 index 4be967ccb803..000000000000 --- a/tools/monzo_ynab/tokens.go +++ /dev/null @@ -1,283 +0,0 @@ -// Creating a Tokens server to manage my access and refresh tokens. Keeping this -// as a separate server allows me to develop and use the access tokens without -// going through client authorization. -package main - -//////////////////////////////////////////////////////////////////////////////// -// Dependencies -//////////////////////////////////////////////////////////////////////////////// - -import ( - "auth" - "encoding/json" - "fmt" - "io" - "kv" - "log" - "net/http" - "net/url" - "os" - "os/signal" - "syscall" - "time" - "utils" -) - -//////////////////////////////////////////////////////////////////////////////// -// Types -//////////////////////////////////////////////////////////////////////////////// - -// This is the response from Monzo's API after we request an access token -// refresh. -type refreshTokenResponse struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ClientId string `json:"client_id"` - ExpiresIn int `json:"expires_in"` -} - -// This is the shape of the request from clients wishing to set state of the -// server. -type setTokensRequest struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int `json:"expires_in"` -} - -// This is our application state. -type state struct { - accessToken string `json:"access_token"` - refreshToken string `json:"refresh_token"` -} - -type readMsg struct { - sender chan state -} - -type writeMsg struct { - state state - sender chan bool -} - -type channels struct { - reads chan readMsg - writes chan writeMsg -} - -//////////////////////////////////////////////////////////////////////////////// -// Top-level Definitions -//////////////////////////////////////////////////////////////////////////////// - -var chans = &channels{ - reads: make(chan readMsg), - writes: make(chan writeMsg), -} - -var ( - monzoClientId = os.Getenv("monzo_client_id") - monzoClientSecret = os.Getenv("monzo_client_secret") - storePath = os.Getenv("store_path") -) - -//////////////////////////////////////////////////////////////////////////////// -// Utils -//////////////////////////////////////////////////////////////////////////////// - -// Print the access and refresh tokens for debugging. -func logTokens(access string, refresh string) { - log.Printf("Access: %s\n", access) - log.Printf("Refresh: %s\n", refresh) -} - -func (state *state) String() string { - return fmt.Sprintf("state{\n\taccessToken: \"%s\",\n\trefreshToken: \"%s\"\n}\n", state.accessToken, state.refreshToken) -} - -// Schedule a token refresh for `expiresIn` seconds using the provided -// `refreshToken`. This will update the application state with the access token -// and schedule an additional token refresh for the newly acquired tokens. -func scheduleTokenRefresh(expiresIn int, refreshToken string) { - duration := time.Second * time.Duration(expiresIn) - timestamp := time.Now().Local().Add(duration) - // TODO(wpcarro): Consider adding a more human readable version that will - // log the number of hours, minutes, etc. until the next refresh. - log.Printf("Scheduling token refresh for %v\n", timestamp) - time.Sleep(duration) - log.Println("Refreshing tokens now...") - accessToken, refreshToken := refreshTokens(refreshToken) - log.Println("Successfully refreshed tokens.") - logTokens(accessToken, refreshToken) - setState(accessToken, refreshToken) -} - -// Exchange existing credentials for a new access token and `refreshToken`. Also -// schedule the next refresh. This function returns the newly acquired access -// token and refresh token. -func refreshTokens(refreshToken string) (string, string) { - // TODO(wpcarro): Support retries with exponential backoff. - res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{ - "grant_type": {"refresh_token"}, - "client_id": {monzoClientId}, - "client_secret": {monzoClientSecret}, - "refresh_token": {refreshToken}, - }) - if res.StatusCode != http.StatusOK { - // TODO(wpcarro): Considering panicking here. - utils.DebugResponse(res) - } - if err != nil { - utils.DebugResponse(res) - log.Fatal("The request to Monzo to refresh our access token failed.", err) - } - defer res.Body.Close() - payload := &refreshTokenResponse{} - err = json.NewDecoder(res.Body).Decode(payload) - if err != nil { - log.Fatal("Could not decode the JSON response from Monzo.", err) - } - - go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken) - - // Interestingly, JSON decoding into the refreshTokenResponse can success - // even if the decoder doesn't populate any of the fields in the - // refreshTokenResponse struct. From what I read, it isn't possible to make - // these fields as required using an annotation, so this guard must suffice - // for now. - if payload.AccessToken == "" || payload.RefreshToken == "" { - log.Fatal("JSON parsed correctly but failed to populate token fields.") - } - - return payload.AccessToken, payload.RefreshToken -} - -func persistTokens(access string, refresh string) { - log.Println("Persisting tokens...") - kv.Set(storePath, "monzoAccessToken", access) - kv.Set(storePath, "monzoRefreshToken", refresh) - log.Println("Successfully persisted tokens.") -} - -// Listen for SIGINT and SIGTERM signals. When received, persist the access and -// refresh tokens and shutdown the server. -func handleInterrupts() { - // Gracefully handle interruptions. - sigs := make(chan os.Signal, 1) - done := make(chan bool) - - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - - go func() { - sig := <-sigs - log.Printf("Received signal to shutdown. %v\n", sig) - state := getState() - persistTokens(state.accessToken, state.refreshToken) - done <- true - }() - - <-done - log.Println("Exiting...") - os.Exit(0) -} - -// Set `accessToken` and `refreshToken` on application state. -func setState(accessToken string, refreshToken string) { - msg := writeMsg{state{accessToken, refreshToken}, make(chan bool)} - chans.writes <- msg - <-msg.sender -} - -// Return our application state. -func getState() state { - msg := readMsg{make(chan state)} - chans.reads <- msg - return <-msg.sender -} - -//////////////////////////////////////////////////////////////////////////////// -// Main -//////////////////////////////////////////////////////////////////////////////// - -func main() { - // Manage application state. - go func() { - state := &state{} - for { - select { - case msg := <-chans.reads: - log.Println("Reading from state...") - log.Println(state) - msg.sender <- *state - case msg := <-chans.writes: - log.Println("Writing to state.") - log.Printf("Old: %s\n", state) - *state = msg.state - log.Printf("New: %s\n", state) - // As an attempt to maintain consistency between application - // state and persisted state, everytime we write to the - // application state, we will write to the store. - persistTokens(state.accessToken, state.refreshToken) - msg.sender <- true - } - } - }() - - // Retrieve cached tokens from store. - accessToken := fmt.Sprintf("%v", kv.Get(storePath, "monzoAccessToken")) - refreshToken := fmt.Sprintf("%v", kv.Get(storePath, "monzoRefreshToken")) - - log.Println("Attempting to retrieve cached credentials...") - logTokens(accessToken, refreshToken) - - if accessToken == "" || refreshToken == "" { - log.Println("Cached credentials are absent. Authorizing client...") - authCode := auth.GetAuthCode(monzoClientId) - tokens := auth.GetTokensFromAuthCode(authCode, monzoClientId, monzoClientSecret) - setState(tokens.AccessToken, tokens.RefreshToken) - go scheduleTokenRefresh(tokens.ExpiresIn, tokens.RefreshToken) - } else { - setState(accessToken, refreshToken) - // If we have tokens, they may be expiring soon. We don't know because - // we aren't storing the expiration timestamp in the state or in the - // store. Until we have that information, and to be safe, let's refresh - // the tokens. - go scheduleTokenRefresh(0, refreshToken) - } - - // Gracefully handle shutdowns. - go handleInterrupts() - - // Listen to inbound requests. - fmt.Println("Listening on http://localhost:4242 ...") - log.Fatal(http.ListenAndServe(":4242", - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.Path == "/refresh-tokens" && req.Method == "POST" { - state := getState() - go scheduleTokenRefresh(0, state.refreshToken) - fmt.Fprintf(w, "Done.") - } else if req.URL.Path == "/set-tokens" && req.Method == "POST" { - // Parse - payload := &setTokensRequest{} - err := json.NewDecoder(req.Body).Decode(payload) - if err != nil { - log.Fatal("Could not decode the user's JSON request.", err) - } - - // Update application state - setState(payload.AccessToken, payload.RefreshToken) - - // Refresh tokens - go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken) - - // Ack - fmt.Fprintf(w, "Done.") - } else if req.URL.Path == "/state" && req.Method == "GET" { - // TODO(wpcarro): Ensure that this returns serialized state. - w.Header().Set("Content-type", "application/json") - state := getState() - payload, _ := json.Marshal(state) - io.WriteString(w, string(payload)) - } else { - log.Printf("Unhandled request: %v\n", *req) - } - }))) -} diff --git a/tools/monzo_ynab/tokens.nix b/tools/monzo_ynab/tokens.nix deleted file mode 100644 index 97de09d741e9..000000000000 --- a/tools/monzo_ynab/tokens.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ depot, briefcase, ... }: - -let - auth = depot.buildGo.package { - name = "auth"; - srcs = [ - ./auth.go - ]; - deps = with briefcase.gopkgs; [ - utils - ]; - }; -in depot.buildGo.program { - name = "token-server"; - srcs = [ - ./tokens.go - ]; - deps = with briefcase.gopkgs; [ - kv - utils - auth - ]; -} diff --git a/tools/monzo_ynab/ynab/client.go b/tools/monzo_ynab/ynab/client.go deleted file mode 100644 index 0492b9071adc..000000000000 --- a/tools/monzo_ynab/ynab/client.go +++ /dev/null @@ -1,24 +0,0 @@ -package client - -import ( - "serde" -) - -// See requests.txt for more details. -func PostTransactions(accountID string, txs []serde.Transaction{}) error { - return map[string]string{ - "transactions": [ - { - "account_id": accountID, - "date": "2019-12-30", - "amount": 10000, - "payee_name": "Richard Stallman", - "memo": "Not so free software after all...", - "cleared": "cleared", - "approved": true, - "flag_color": "red", - "import_id": "xyz-123" - } - ] - } -} diff --git a/tools/monzo_ynab/ynab/serde.go b/tools/monzo_ynab/ynab/serde.go deleted file mode 100644 index 53dd33e83637..000000000000 --- a/tools/monzo_ynab/ynab/serde.go +++ /dev/null @@ -1,52 +0,0 @@ -// This package hosts the serialization and deserialization logic for all of the -// data types with which our application interacts from the YNAB API. -package main - -import ( - "encoding/json" - "fmt" - "time" -) - -type Transaction struct { - Id string `json:"id"` - Date time.Time `json:"date"` - Amount int `json:"amount"` - Memo string `json:"memo"` - Cleared string `json:"cleared"` - Approved bool `json:"approved"` - FlagColor string `json:"flag_color"` - AccountId string `json:"account_id"` - AccountName string `json:"account_name"` - PayeeId string `json:"payeed_id"` - PayeeName string `json:"payee_name"` - CategoryId string `json:"category_id"` - CategoryName string `json:"category_name"` - Deleted bool `json:"deleted"` - // TransferAccountId interface{} `json:"transfer_account_id"` - // TransferTransactionId interface{} `json:"transfer_transaction_id"` - // MatchedTransactionId interface{} `json:"matched_transaction_id"` - // ImportId interface{} `json:"import_id"` - // Subtransactions interface{} `json:"subtransactions"` -} - -// Attempts to encode a YNAB transaction into a string. -func serializeTx(tx *Transaction) (string, error) { - x, err := json.Marshal(tx) - return string(x), err -} - -// Attempts to parse a string encoding a transaction presumably sent from a -// YNAB server. -func deserializeTx(x string) (*Transaction, error) { - target := &Transaction{} - err := json.Unmarshal([]byte(x), target) - return target, err -} - -func main() { - target, _ := deserializeTx(tx) - out, _ := serializeTx(target) - fmt.Println(out) - fmt.Println(ynabOut) -} |