Skip to content

Commit

Permalink
Async transactions fection and modularising templates (#10)
Browse files Browse the repository at this point in the history
* adding in endpoint for endpoint generation

* updated files?

* a whole lotta work

* formatting
  • Loading branch information
ESteanes authored Sep 9, 2024
1 parent ed3a9c4 commit e591b2a
Show file tree
Hide file tree
Showing 12 changed files with 762 additions and 237 deletions.
70 changes: 58 additions & 12 deletions datafetcher/handlers/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"
"net/http"

"github.com/a-h/templ"
"github.com/esteanes/expense-manager/datafetcher/templates"
"github.com/esteanes/expense-manager/datafetcher/upclient"
)
Expand All @@ -17,27 +18,72 @@ type AccountHandler struct {
func NewAccountHandler(log *log.Logger, upclient *upclient.APIClient, auth context.Context) *AccountHandler {
handler := &AccountHandler{}
handler.BaseHandler = &BaseHandler{
Uri: "/accounts",
Log: log,
UpClient: upclient,
UpAuth: auth,
Handler: handler, // Set the Handler interface to the specific handler
Uri: "/accounts",
Log: log,
UpClient: upclient,
UpAuth: auth,
Handler: handler, // Set the Handler interface to the specific handler
MaxPageSize: int32(100),
}
return handler
}

func (h *AccountHandler) Post(w http.ResponseWriter, r *http.Request) {}
func (h *AccountHandler) Get(w http.ResponseWriter, r *http.Request) {
pageSize := int32(30)
filterOwnershipType := upclient.OwnershipTypeEnum("INDIVIDUAL")
resp, r2, err := h.UpClient.AccountsAPI.AccountsGet(h.UpAuth).PageSize(pageSize).FilterOwnershipType(filterOwnershipType).Execute()
h.Log.Println(resp)
accountChannel := make(chan upclient.AccountResource, 10)
clonedChannels := Clone(accountChannel, 2)
accountChannel1 := <-clonedChannels
accountChannel2 := <-clonedChannels
go h.GetAccounts(accountChannel, filterOwnershipType)

templ.Handler(templates.Accounts(accountChannel1, accountChannel2), templ.WithStreaming()).ServeHTTP(w, r)
}

func (h *AccountHandler) GetAccounts(accountChannel chan upclient.AccountResource, ownershipType upclient.OwnershipTypeEnum) {
defer close(accountChannel)
resp, r2, err := h.UpClient.AccountsAPI.AccountsGet(h.UpAuth).PageSize(h.MaxPageSize).FilterOwnershipType(ownershipType).Execute()
if err != nil {
fmt.Fprintf(w, "Error when calling `AccountsAPI.AccountsGet``: %v\n", err)
fmt.Fprintf(w, "Full HTTP response: %v\n", r2)
h.Log.Println(fmt.Sprintf("Error when calling `AccountsAPI.AccountsGet`: %s\n", err))
h.Log.Println(fmt.Sprintf("Full HTTP response: %v\n", r2))
h.Log.Println("Unable to get account information")
}

// fmt.Fprintf(w, "Response from `AccountsAPI.AccountsGet`: %v\n", resp)
templates.Accounts(resp.Data).Render(r.Context(), w)
for _, account := range resp.Data {
accountChannel <- account
}
}
func Clone[T any](inCh chan T, size int) <-chan <-chan T {
// The channel of channels to return at the end of this function call
ret := make(chan (<-chan T), size)

// This slice keeps track of all the output channels this function will be creating below.
outChs := make([]chan T, size)

// Create channels, keep track of them in the slice and send them on the return channel
for i := 0; i < size; i++ {
// The buffer size of the newly created channel is the same as the input channel
outChs[i] = make(chan T, cap(inCh))
ret <- outChs[i]
}

// Start a goroutine to manage receiving message from the input channels and sending out to the output channels
// Close the output channels if the input channel has been closed.
go func() {
for {
msg, more := <-inCh
if more {
for _, ch := range outChs {
ch <- msg
}
} else {
for _, ch := range outChs {
close(ch)
}
return
}
}
}()

return ret
}
13 changes: 8 additions & 5 deletions datafetcher/handlers/basehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"context"
"fmt"
"log"
"net/http"

Expand All @@ -14,14 +15,16 @@ type Handler interface {
}

type BaseHandler struct {
Uri string
Log *log.Logger
UpClient *upclient.APIClient
UpAuth context.Context
Handler Handler // Embed the Handler interface
Uri string
Log *log.Logger
UpClient *upclient.APIClient
UpAuth context.Context
Handler Handler // Embed the Handler interface
MaxPageSize int32
}

func (h *BaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Log.Println(fmt.Sprintf("%s %s params: %s", r.Method, h.Uri, r.URL.Query()))
switch r.Method {
case http.MethodPost:
h.Handler.Post(w, r)
Expand Down
86 changes: 72 additions & 14 deletions datafetcher/handlers/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,95 @@ import (
"fmt"
"log"
"net/http"
"net/url"
"strconv"

"github.com/a-h/templ"
"github.com/esteanes/expense-manager/datafetcher/templates"

"github.com/esteanes/expense-manager/datafetcher/upclient"
)

type TransactionsHandler struct {
*BaseHandler
*AccountHandler
}

func NewTransactionHandler(log *log.Logger, upclient *upclient.APIClient, auth context.Context) *TransactionsHandler {
func NewTransactionHandler(log *log.Logger, upclient *upclient.APIClient, auth context.Context, accountHandler *AccountHandler) *TransactionsHandler {
handler := &TransactionsHandler{}
handler.BaseHandler = &BaseHandler{
Uri: "/transactions",
Log: log,
UpClient: upclient,
UpAuth: auth,
Handler: handler, // Set the Handler interface to the specific handler
Uri: "/transactions",
Log: log,
UpClient: upclient,
UpAuth: auth,
Handler: handler, // Set the Handler interface to the specific handler
MaxPageSize: int32(100),
}
handler.AccountHandler = accountHandler
return handler
}

func (h *TransactionsHandler) Post(w http.ResponseWriter, r *http.Request) {}
func (h *TransactionsHandler) Get(w http.ResponseWriter, r *http.Request) {
pageSize := int32(30) // int32 | The number of records to return in each page. (optional)
resp, r2, err := h.UpClient.TransactionsAPI.TransactionsGet(h.UpAuth).PageSize(pageSize).Execute()
queryParams := r.URL.Query()
numTransactions, err := strconv.ParseInt(queryParams.Get("numTransactions"), 10, 32)
if err != nil {
fmt.Fprintf(w, "Error when calling `TransactionsAPI.TransactionsGet``: %v\n", err)
fmt.Fprintf(w, "Full HTTP response: %v\n", r2.Body)
h.Log.Println(r2.Body)
numTransactions = int64(10)
}
accountId := queryParams.Get("accountId")
transactionsChannel := make(chan upclient.TransactionResource, numTransactions)
go h.getTransactions(transactionsChannel, int32(numTransactions), accountId)
accountsChannel := make(chan upclient.AccountResource)
go h.AccountHandler.GetAccounts(accountsChannel, upclient.OwnershipTypeEnum("INDIVIDUAL"))
templ.Handler(templates.Transactions(transactionsChannel, accountsChannel), templ.WithStreaming()).ServeHTTP(w, r)

}
func (h *TransactionsHandler) getTransactions(transactionsChannel chan upclient.TransactionResource, numTransactions int32, accountId string) {
defer close(transactionsChannel)
getRequest := h.UpClient.TransactionsAPI.AccountsAccountIdTransactionsGet(h.UpAuth, accountId).PageSize(h.MaxPageSize)
var pageAfter *string
pageAfter = nil
countTransactions := int32(0)
for countTransactions < numTransactions {
if pageAfter != nil {
pageKeyParsed, _ := ExtractPageAfter(*pageAfter)
getRequest = getRequest.PageAfter(pageKeyParsed)
h.Log.Println(fmt.Sprintf("setting pageAfter to %s", pageKeyParsed))
}
h.Log.Println(fmt.Sprintf("request struct is %+v", getRequest))
resp, r2, err := getRequest.Execute()
if err != nil {
h.Log.Println(fmt.Sprintf("Error when calling `TransactionsAPI.TransactionsGet``: %s\n", err))
h.Log.Println(fmt.Sprintf("Full HTTP response: %s\n", r2.Body))
h.Log.Println(r2.Body)
return
}
// h.Log.Println(fmt.Sprintf("resp object is %#v", resp))
pageAfter = resp.Links.Next.Get()
h.Log.Println(fmt.Sprintf("pageAfter is %p", pageAfter))
if pageAfter != nil {
h.Log.Println(fmt.Sprintf("page after link is: %s", *pageAfter))
}
for _, transaction := range resp.Data {
if countTransactions < numTransactions {
transactionsChannel <- transaction
countTransactions++
}
}
}
// response from `TransactionsGet`: ListTransactionsResponse
templates.Transactions(resp.Data).Render(r.Context(), w)
}

func ExtractPageAfter(inputURL string) (string, error) {
// Parse the URL
parsedURL, err := url.Parse(inputURL)
if err != nil {
return "", err
}

// Extract the query parameters
queryParams := parsedURL.Query()

// Get the value of "page[before]"
pageAfter := queryParams.Get("page[after]")

return pageAfter, nil
}
2 changes: 1 addition & 1 deletion datafetcher/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func HandleRequests(upBankToken string, log *log.Logger) {

// Creating individual handlers
accountHandler := handlers.NewAccountHandler(log, apiClient, auth)
transactionsHandler := handlers.NewTransactionHandler(log, apiClient, auth)
transactionsHandler := handlers.NewTransactionHandler(log, apiClient, auth, accountHandler)
transactionsCsvHandler := handlers.NewTransactionCsvHandler(log, apiClient, auth)
mux := http.NewServeMux()
mux.HandleFunc(accountHandler.Uri, accountHandler.ServeHTTP)
Expand Down
45 changes: 32 additions & 13 deletions datafetcher/templates/accounts.templ
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,36 @@ import (
"strings"
)

templ Accounts(accounts []upclient.AccountResource) {
templ AccountDetails(accounts <-chan upclient.AccountResource) {
<h1>Accounts</h1>
<ul>
for account := range accounts {
@templ.Flush() {
<div>
<h2>{ account.Attributes.DisplayName }</h2>
<p>Account Type: { strings.ToTitle(string(account.Attributes.AccountType)) }</p>
<p>Balance: { account.Attributes.Balance.CurrencyCode } { account.Attributes.Balance.Value }</p>
<p>Created At: { account.Attributes.CreatedAt.String() }</p>
<p>Id: { account.Id }</p>
</div>
<hr/>
}
}
</ul>
}

templ AccountButtons(accounts <-chan upclient.AccountResource) {
<h1>Accounts</h1>
<ul>
for account := range accounts {
@templ.Flush() {
<button value={ account.Id }>{ account.Attributes.DisplayName } { account.Attributes.Balance.Value } { account.Attributes.Balance.CurrencyCode }</button>
}
}
</ul>
}

templ Accounts(accounts1 <-chan upclient.AccountResource, accounts2 <-chan upclient.AccountResource) {
<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -14,18 +43,8 @@ templ Accounts(accounts []upclient.AccountResource) {
<title>Accounts</title>
</head>
<body>
<h1>Accounts</h1>
<ul>
for _, account := range accounts {
<div>
<h2>{ account.Attributes.DisplayName }</h2>
<p>Account Type: { strings.ToTitle(string(account.Attributes.AccountType)) }</p>
<p>Balance: { account.Attributes.Balance.CurrencyCode } { account.Attributes.Balance.Value }</p>
<p>Created At: { account.Attributes.CreatedAt.String() }</p>
</div>
<hr/>
}
</ul>
@AccountButtons(accounts2)
@AccountDetails(accounts1)
</body>
</html>
}
Loading

0 comments on commit e591b2a

Please sign in to comment.