diff --git a/datafetcher/functions/functions.go b/datafetcher/functions/functions.go index c9fcb69..aaf2377 100644 --- a/datafetcher/functions/functions.go +++ b/datafetcher/functions/functions.go @@ -1,6 +1,78 @@ 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 + } + log.Println(fmt.Sprintf("Parsed date is %s", timeStartDate)) + log.Println(fmt.Sprintf("Error in parsing date is %s", err)) + } + + // 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 +} diff --git a/datafetcher/handlers/transactions-csv.go b/datafetcher/handlers/transactions-csv.go index 1f16384..b3eddc3 100644 --- a/datafetcher/handlers/transactions-csv.go +++ b/datafetcher/handlers/transactions-csv.go @@ -7,14 +7,16 @@ 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", @@ -22,40 +24,46 @@ func NewTransactionCsvHandler(log *log.Logger, upclient *upclient.APIClient, aut 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) + } } } diff --git a/datafetcher/handlers/transactions.go b/datafetcher/handlers/transactions.go index 5316df3..98c8c54 100644 --- a/datafetcher/handlers/transactions.go +++ b/datafetcher/handlers/transactions.go @@ -6,7 +6,6 @@ import ( "log" "net/http" "net/url" - "strconv" "github.com/a-h/templ" "github.com/esteanes/expense-manager/datafetcher/functions" @@ -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.FilterSince(*queryParams.StartDate) + } + if queryParams.EndDate != nil { + 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 { @@ -80,7 +82,7 @@ func (h *TransactionsHandler) getTransactionsForAllAccounts(transactionsChannel 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++ } @@ -91,13 +93,23 @@ func (h *TransactionsHandler) getTransactionsForAllAccounts(transactionsChannel } } -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 { @@ -114,16 +126,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") diff --git a/datafetcher/server.go b/datafetcher/server.go index 35eff41..2f19aee 100644 --- a/datafetcher/server.go +++ b/datafetcher/server.go @@ -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) diff --git a/datafetcher/templates/transactions.templ b/datafetcher/templates/transactions.templ index 45e65a6..56016d9 100644 --- a/datafetcher/templates/transactions.templ +++ b/datafetcher/templates/transactions.templ @@ -1,6 +1,11 @@ package templates -import "github.com/esteanes/expense-manager/datafetcher/upclient" +import ( + "github.com/esteanes/expense-manager/datafetcher/functions" + "github.com/esteanes/expense-manager/datafetcher/upclient" + "strconv" + "time" +) func getMessage(transaction upclient.TransactionResource) string { maybeString := transaction.Attributes.Message.Get() @@ -39,9 +44,23 @@ func getCategory(transaction upclient.TransactionResource) string { } +func getDateOrDefault(dateTime *time.Time, defaultTime time.Time) string { + if dateTime == nil { + return defaultTime.Format("2006-01-02") + } + return dateTime.Format("2006-01-02") +} + templ TransactionsTable(transactions chan upclient.TransactionResource) {