Skip to content

Commit

Permalink
Query param parsing, download button and Date filtering (#18)
Browse files Browse the repository at this point in the history
* a lot

* fixing small issues
  • Loading branch information
ESteanes authored Oct 6, 2024
1 parent b441fd9 commit 3e14ff0
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 88 deletions.
70 changes: 70 additions & 0 deletions datafetcher/functions/functions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,76 @@
package functions

import (
"fmt"
"log"
"net/url"
"os"
"strconv"
"time"
)

const (
AccountIdQueryParam = "accountId"
TransactionNumQueryParam = "numTransactions"
StartDateParam = "startDate"
EndDateParam = "endDate"
)

// QueryParams struct to hold the query parameters
type QueryParams struct {
AccountID *string
NumTransactions *int32
StartDate *time.Time
EndDate *time.Time
}

func (q *QueryParams) String() string {
return fmt.Sprintf("QueryParams{AccountID: %s, NumTransactions: %d, StartDate: %s, EndDate: %s}", *q.AccountID, *q.NumTransactions, *q.StartDate, *q.EndDate)
}

// FetchQueryParams extracts query parameters from the url.Values map
func FetchQueryParams(queryParams url.Values) *QueryParams {
params := &QueryParams{}
defaultTransactions := int32(20)
params.NumTransactions = &defaultTransactions
logger := log.New(os.Stdout, "query params:", log.Default().Flags())

// Fetch "accountId"
if accountID := queryParams.Get(AccountIdQueryParam); accountID != "" {
params.AccountID = &accountID
}

// Fetch "numTransactions"
if numTransactions := queryParams.Get(TransactionNumQueryParam); numTransactions != "" {
if num, err := strconv.Atoi(numTransactions); err == nil {
myInt := int32(num)
params.NumTransactions = &myInt
}
}

// Fetch "startDate"
if startDate := queryParams.Get(StartDateParam); startDate != "" {
log.Println(fmt.Sprintf("Start date is %s", startDate))
timeStartDate, err := time.Parse(time.RFC3339, startDate)
if err == nil {
params.StartDate = &timeStartDate
}
}

// Fetch "endDate"
if endDate := queryParams.Get(EndDateParam); endDate != "" {
timeEndDate, err := time.Parse(time.RFC3339, endDate)
if err == nil {
params.EndDate = &timeEndDate
}
}

if params.StartDate != nil && params.EndDate != nil {
maxTransactions := int32(10000)
params.NumTransactions = &maxTransactions
}

logger.Println(fmt.Sprintf("query params are: %v", params))

return params
}
52 changes: 30 additions & 22 deletions datafetcher/handlers/transactions-csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,63 @@ import (
"log"
"net/http"

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

type TransactionsCsvHandler struct {
*BaseHandler
*TransactionsHandler
}

func NewTransactionCsvHandler(log *log.Logger, upclient *upclient.APIClient, auth context.Context) *TransactionsCsvHandler {
func NewTransactionCsvHandler(log *log.Logger, upclient *upclient.APIClient, auth context.Context, transactionsHandler *TransactionsHandler) *TransactionsCsvHandler {
handler := &TransactionsCsvHandler{}
handler.BaseHandler = &BaseHandler{
Uri: "/transactions-csv",
Log: log,
UpClient: upclient,
UpAuth: auth,
Handler: handler}
handler.TransactionsHandler = transactionsHandler
return handler
}

func (h *TransactionsCsvHandler) Post(w http.ResponseWriter, r *http.Request) {}
func (h *TransactionsCsvHandler) Get(w http.ResponseWriter, r *http.Request) {
queryParams := functions.FetchQueryParams(r.URL.Query())
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", "attachment;filename=transactions.csv")
csvWriter := csv.NewWriter(w)
pageSize := int32(30) // int32 | The number of records to return in each page. (optional)
resp2, _, _ := h.UpClient.TransactionsAPI.TransactionsGet(h.UpAuth).PageSize(pageSize).Execute()
transactionsChannel := make(chan upclient.TransactionResource, *queryParams.NumTransactions)
if queryParams.AccountID == nil {
go h.getTransactionsForAllAccounts(transactionsChannel, queryParams)
} else {
go h.getTransactionsForSpecifiedAccount(transactionsChannel, queryParams)

header := []string{"Transaction Description", "Amount", "Date"}
header := []string{"Transaction Description", "Amount", "Date"}

h.Log.Printf("trying to generate a CSV")
// Print the JSON-formatted response
if err := csvWriter.Write(header); err != nil {
http.Error(w, "Error writing CSV header", http.StatusInternalServerError)
return
}
for _, transaction := range resp2.Data {
record := []string{
transaction.Attributes.Description,
transaction.Attributes.Amount.Value,
transaction.Attributes.CreatedAt.String(),
}
if err := csvWriter.Write(record); err != nil {
fmt.Fprintf(w, "Error writing CSV line %v\n", err)
h.Log.Printf("trying to generate a CSV")
// Print the JSON-formatted response
if err := csvWriter.Write(header); err != nil {
http.Error(w, "Error writing CSV header", http.StatusInternalServerError)
return
}
}
for transaction := range transactionsChannel {
record := []string{
transaction.Attributes.Description,
transaction.Attributes.Amount.Value,
transaction.Attributes.CreatedAt.String(),
}
if err := csvWriter.Write(record); err != nil {
fmt.Fprintf(w, "Error writing CSV line %v\n", err)
return
}
}

csvWriter.Flush()
csvWriter.Flush()

if err := csvWriter.Error(); err != nil {
fmt.Fprintf(w, "Error flushing CSV writer: %v\n", err)
if err := csvWriter.Error(); err != nil {
fmt.Fprintf(w, "Error flushing CSV writer: %v\n", err)
}
}
}
68 changes: 41 additions & 27 deletions datafetcher/handlers/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"log"
"net/http"
"net/url"
"strconv"

"github.com/a-h/templ"
"github.com/esteanes/expense-manager/datafetcher/functions"
Expand Down Expand Up @@ -35,30 +34,33 @@ func NewTransactionHandler(log *log.Logger, upclient *upclient.APIClient, auth c

func (h *TransactionsHandler) Post(w http.ResponseWriter, r *http.Request) {}
func (h *TransactionsHandler) Get(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()
numTransactions, err := strconv.ParseInt(queryParams.Get(functions.TransactionNumQueryParam), 10, 32)
if err != nil {
numTransactions = int64(10)
}
transactionsChannel := make(chan upclient.TransactionResource, numTransactions)
accountId := queryParams.Get(functions.AccountIdQueryParam)
if accountId == "" {
go h.getTransactionsForAllAccounts(transactionsChannel, int32(numTransactions))
queryParams := functions.FetchQueryParams(r.URL.Query())
transactionsChannel := make(chan upclient.TransactionResource, *queryParams.NumTransactions)
if queryParams.AccountID == nil {
go h.getTransactionsForAllAccounts(transactionsChannel, queryParams)
} else {
go h.getTransactionsForSpecifiedAccount(transactionsChannel, int32(numTransactions), accountId)
go h.getTransactionsForSpecifiedAccount(transactionsChannel, queryParams)
}
accountsChannel := make(chan upclient.AccountResource)
go h.AccountHandler.GetAccounts(accountsChannel, upclient.OwnershipTypeEnum("INDIVIDUAL"))
templ.Handler(templates.Transactions("Transactions", transactionsChannel, accountsChannel, strconv.Itoa(int(numTransactions))), templ.WithStreaming()).ServeHTTP(w, r)
templ.Handler(templates.Transactions("Transactions", transactionsChannel, accountsChannel, queryParams), templ.WithStreaming()).ServeHTTP(w, r)

}
func (h *TransactionsHandler) getTransactionsForAllAccounts(transactionsChannel chan upclient.TransactionResource, numTransactions int32) {
func (h *TransactionsHandler) getTransactionsForAllAccounts(transactionsChannel chan upclient.TransactionResource, queryParams *functions.QueryParams) {
defer close(transactionsChannel)
getRequest := h.UpClient.TransactionsAPI.TransactionsGet(h.UpAuth).PageSize(h.MaxPageSize)

if queryParams.StartDate != nil {
getRequest = getRequest.FilterSince(*queryParams.StartDate)
}
if queryParams.EndDate != nil {
getRequest = getRequest.FilterUntil(*queryParams.EndDate)
}

var pageAfter *string
pageAfter = nil
countTransactions := int32(0)
for countTransactions < numTransactions {
for countTransactions < *queryParams.NumTransactions {
if pageAfter != nil {
pageKeyParsed, err := ExtractPageAfter(*pageAfter)
if err != nil {
Expand All @@ -75,29 +77,40 @@ func (h *TransactionsHandler) getTransactionsForAllAccounts(transactionsChannel
}
return
}
pageAfter = resp.Links.Next.Get()
if pageAfter != nil {
h.Log.Println(fmt.Sprintf("page after link is: %s", *pageAfter))
}
for _, transaction := range resp.Data {
if countTransactions < numTransactions {
if countTransactions < *queryParams.NumTransactions {
transactionsChannel <- transaction
countTransactions++
}
}
pageAfter = resp.Links.Next.Get()
if pageAfter == nil {
break
}
h.Log.Println(fmt.Sprintf("page after link is: %s", *pageAfter))
}
if pageAfter == nil {
h.Log.Println("You have reached the end of all transactions")
}
}

func (h *TransactionsHandler) getTransactionsForSpecifiedAccount(transactionsChannel chan upclient.TransactionResource, numTransactions int32, accountId string) {
func (h *TransactionsHandler) getTransactionsForSpecifiedAccount(transactionsChannel chan upclient.TransactionResource, queryParams *functions.QueryParams) {
defer close(transactionsChannel)
getRequest := h.UpClient.TransactionsAPI.AccountsAccountIdTransactionsGet(h.UpAuth, accountId).PageSize(h.MaxPageSize)
getRequest := h.UpClient.TransactionsAPI.AccountsAccountIdTransactionsGet(h.UpAuth, *queryParams.AccountID).PageSize(h.MaxPageSize)

if queryParams.StartDate != nil {
h.Log.Println(fmt.Sprintf("Setting Filter Since to: %s", *queryParams.StartDate))
getRequest = getRequest.FilterSince(*queryParams.StartDate)
}
if queryParams.EndDate != nil {
h.Log.Println(fmt.Sprintf("Setting Filter Until to: %s", *queryParams.EndDate))
getRequest = getRequest.FilterUntil(*queryParams.EndDate)
}

var pageAfter *string
pageAfter = nil
countTransactions := int32(0)
for countTransactions < numTransactions {
for countTransactions < *queryParams.NumTransactions {
if pageAfter != nil {
pageKeyParsed, err := ExtractPageAfter(*pageAfter)
if err != nil {
Expand All @@ -114,16 +127,17 @@ func (h *TransactionsHandler) getTransactionsForSpecifiedAccount(transactionsCha
}
return
}
pageAfter = resp.Links.Next.Get()
if pageAfter != nil {
h.Log.Println(fmt.Sprintf("page after link is: %s", *pageAfter))
}
for _, transaction := range resp.Data {
if countTransactions < numTransactions {
if countTransactions < *queryParams.NumTransactions {
transactionsChannel <- transaction
countTransactions++
}
}
pageAfter = resp.Links.Next.Get()
if pageAfter == nil {
break
}
h.Log.Println(fmt.Sprintf("page after link is: %s", *pageAfter))
}
if pageAfter == nil {
h.Log.Println("You have reached the end of all transactions")
Expand Down
2 changes: 1 addition & 1 deletion datafetcher/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func HandleRequests(upBankToken string, log *log.Logger) {
// Creating individual handlers
accountHandler := handlers.NewAccountHandler(log, apiClient, auth)
transactionsHandler := handlers.NewTransactionHandler(log, apiClient, auth, accountHandler)
transactionsCsvHandler := handlers.NewTransactionCsvHandler(log, apiClient, auth)
transactionsCsvHandler := handlers.NewTransactionCsvHandler(log, apiClient, auth, transactionsHandler)
staticFileHandler := handlers.NewStaticFileHandler(log)
mux := http.NewServeMux()
mux.HandleFunc(accountHandler.Uri, accountHandler.ServeHTTP)
Expand Down
Loading

0 comments on commit 3e14ff0

Please sign in to comment.