Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query param parsing, download button and Date filtering #18

Merged
merged 2 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading