parent
36f751a69f
commit
4aa8a2f822
@ -0,0 +1,33 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserSchema struct {
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
Email string `json:"email" bson:"email"`
|
||||||
|
Password string `json:"password" bson:"password"`
|
||||||
|
Session string `json:"session" bson:"session"`
|
||||||
|
Balance money.Money `json:"balance" bson:"balance"`
|
||||||
|
Budget money.Money `json:"budget" bson:"budget"`
|
||||||
|
Categories map[string]money.Money `json:"categories" bson:"categories"`
|
||||||
|
Expenses []Transaction `json:"expenses" bson:"expenses"`
|
||||||
|
Income []Transaction `json:"income" bson:"income"`
|
||||||
|
RecurringExpenses []RecurringTransaction `json:"recurring_expenses" bson:"recurring_expenses"`
|
||||||
|
RecurringIncome []RecurringTransaction `json:"recurring_income" bson:"recurring_income"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
Timestamp int64 `json:"timestamp" bson:"timestamp"`
|
||||||
|
Category string `json:"category" bson:"category"`
|
||||||
|
Amount money.Money `json:"amount" bson:"amount"`
|
||||||
|
Type string `json:"type" bson:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecurringTransaction struct {
|
||||||
|
Transaction `json:"transaction" bson:"transaction"`
|
||||||
|
Period int `json:"period" bson:"period"` // in days
|
||||||
|
Since int64 `json:"since" bson:"since"` // unix timestamp
|
||||||
|
Until int64 `json:"until" bson:"until"` // 0 for no end date
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package money
|
||||||
|
|
||||||
|
type Currency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
USD Currency = "USD"
|
||||||
|
CAD = "CAD"
|
||||||
|
)
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package money
|
||||||
|
|
||||||
|
type Money struct {
|
||||||
|
Currency Currency `json:"currency" bson:"currency"`
|
||||||
|
Whole int `json:"whole" bson:"whole"`
|
||||||
|
Decimal int `json:"decimal" bson:"decimal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// add x to y
|
||||||
|
func Add(x, y Money) Money {
|
||||||
|
x.Decimal += y.Decimal
|
||||||
|
if x.Decimal >= 100 {
|
||||||
|
x.Whole++
|
||||||
|
x.Decimal -= 100
|
||||||
|
}
|
||||||
|
x.Whole += y.Whole
|
||||||
|
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// subtract x from y
|
||||||
|
func Subtract(x, y Money) Money {
|
||||||
|
x.Decimal = y.Decimal - x.Decimal
|
||||||
|
if x.Decimal < 0 {
|
||||||
|
x.Whole++
|
||||||
|
x.Decimal += 100
|
||||||
|
}
|
||||||
|
x.Whole = y.Whole - x.Whole
|
||||||
|
|
||||||
|
return x
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserInfoResponse struct {
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
Email string `json:"email" bson:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
found := userCollection.FindOne(r.Context(), bson.D{primitive.E{Key: "session", Value: strings.ToLower(session)}})
|
||||||
|
if found.Err() != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"session key invalid\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := found.Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem decoding user\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info := UserInfoResponse{
|
||||||
|
Name: user.Name,
|
||||||
|
Email: user.Email,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := json.Marshal(info)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
}
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Put user info fetching code into middleware
|
||||||
|
|
||||||
|
type GetBalanceResponse struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
Data money.Money `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBalance(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := GetBalanceResponse{
|
||||||
|
Status: 200,
|
||||||
|
RequestID: "0",
|
||||||
|
Data: money.Money{
|
||||||
|
Currency: user.Balance.Currency,
|
||||||
|
Whole: user.Balance.Whole,
|
||||||
|
Decimal: user.Balance.Decimal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetBalance(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get form values
|
||||||
|
r.ParseForm()
|
||||||
|
newWhole, err := strconv.Atoi(r.Form.Get("whole"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect whole value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newDecimal, err := strconv.Atoi(r.Form.Get("decimal"))
|
||||||
|
if err != nil || newDecimal < 0 || newDecimal > 99 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect decimal value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: figure out how to efficiently determing currency
|
||||||
|
newBalance := money.Money{
|
||||||
|
Currency: "USD",
|
||||||
|
Whole: newWhole,
|
||||||
|
Decimal: newDecimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "balance", Value: newBalance}}}}
|
||||||
|
_, err = userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := GetBalanceResponse{
|
||||||
|
Status: 200,
|
||||||
|
RequestID: "0",
|
||||||
|
Data: money.Money{
|
||||||
|
Currency: newBalance.Currency,
|
||||||
|
Whole: newBalance.Whole,
|
||||||
|
Decimal: newBalance.Decimal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addToBalance(user db.UserSchema, amount money.Money) bool {
|
||||||
|
|
||||||
|
newBalance := money.Add(amount, user.Balance)
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: user.Session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "balance", Value: newBalance}}}}
|
||||||
|
_, err := userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func subtractFromBalance(user db.UserSchema, amount money.Money) bool {
|
||||||
|
|
||||||
|
// create money object to store in db
|
||||||
|
newBalance := money.Subtract(amount, user.Balance)
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: user.Session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "balance", Value: newBalance}}}}
|
||||||
|
_, err := userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
@ -0,0 +1,173 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BudgetResponse struct {
|
||||||
|
// total amount allowed to spend in a month
|
||||||
|
Budget money.Money `json:"budget"`
|
||||||
|
// total amount allowed to spend by category
|
||||||
|
BudgetCategories map[string]money.Money `json:"budget_categories"`
|
||||||
|
// categories in the budget
|
||||||
|
Categories []string `json:"categories"`
|
||||||
|
// transactions mapped to a category
|
||||||
|
Expenses map[string][]db.Transaction `json:"expenses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBudget(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var response BudgetResponse
|
||||||
|
|
||||||
|
response.Budget = user.Budget
|
||||||
|
response.BudgetCategories = user.Categories
|
||||||
|
cats := make([]string, len(user.Categories))
|
||||||
|
i := 0
|
||||||
|
for k := range user.Categories {
|
||||||
|
cats[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
response.Categories = cats
|
||||||
|
response.Expenses = make(map[string][]db.Transaction)
|
||||||
|
for _, e := range user.Expenses {
|
||||||
|
if response.Expenses[e.Category] == nil {
|
||||||
|
response.Expenses[e.Category] = make([]db.Transaction, 0)
|
||||||
|
}
|
||||||
|
response.Expenses[e.Category] = append(response.Expenses[e.Category], e)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
|
||||||
|
}
|
||||||
|
func SetCategoryBudget(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get form values
|
||||||
|
r.ParseForm()
|
||||||
|
cat := r.FormValue("category")
|
||||||
|
if cat == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"category must be specified\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newWhole, err := strconv.Atoi(r.Form.Get("whole"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect whole value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newDecimal, err := strconv.Atoi(r.Form.Get("decimal"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect decimal value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: figure out how to efficiently determing currency
|
||||||
|
newBudget := money.Money{
|
||||||
|
Currency: money.Currency(r.FormValue("currency")),
|
||||||
|
Whole: newWhole,
|
||||||
|
Decimal: newDecimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Categories == nil {
|
||||||
|
user.Categories = make(map[string]money.Money)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Categories[cat] = newBudget
|
||||||
|
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "categories", Value: user.Categories}}}}
|
||||||
|
_, err = userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte("{\"status\":200}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetBudget(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get form values
|
||||||
|
r.ParseForm()
|
||||||
|
newWhole, err := strconv.Atoi(r.Form.Get("whole"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect whole value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newDecimal, err := strconv.Atoi(r.Form.Get("decimal"))
|
||||||
|
if err != nil || newDecimal < 0 || newDecimal > 99 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect decimal value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: figure out how to efficiently determing currency
|
||||||
|
newBudget := money.Money{
|
||||||
|
Currency: money.Currency(r.FormValue("currency")),
|
||||||
|
Whole: newWhole,
|
||||||
|
Decimal: newDecimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "budget", Value: newBudget}}}}
|
||||||
|
_, err = userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte("{\"status\":200}"))
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMonthExpenses(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Expenses == nil {
|
||||||
|
user.Expenses = make([]db.Transaction, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := money.Money{}
|
||||||
|
total.Currency = user.Balance.Currency
|
||||||
|
|
||||||
|
for i := 0; i < len(user.Expenses); i++ {
|
||||||
|
// stop if/when we get past a month ago
|
||||||
|
if user.Expenses[i].Timestamp < time.Now().Add(-30*24*time.Hour).Unix() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
total = money.Add(total, user.Expenses[i].Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := json.Marshal(total)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMonthIncome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Income == nil {
|
||||||
|
user.Income = make([]db.Transaction, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := money.Money{}
|
||||||
|
total.Currency = user.Balance.Currency
|
||||||
|
|
||||||
|
for i := 0; i < len(user.Income); i++ {
|
||||||
|
// stop if/when we get past a month ago
|
||||||
|
if user.Income[i].Timestamp < time.Now().Add(-30*24*time.Hour).Unix() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
total = money.Add(total, user.Income[i].Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := json.Marshal(total)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import "github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
|
func Router() *chi.Mux {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
// balance widget
|
||||||
|
r.Get("/balance", GetBalance)
|
||||||
|
r.Post("/balance", SetBalance)
|
||||||
|
|
||||||
|
// transaction widget
|
||||||
|
r.Get("/transactions/recent", GetRecentTransactions)
|
||||||
|
r.Post("/transactions", NewTransaction)
|
||||||
|
r.Post("/transactions/recurring", NewRecurring)
|
||||||
|
|
||||||
|
// budget widget
|
||||||
|
r.Get("/budget", GetBudget)
|
||||||
|
r.Post("/budget", SetBudget)
|
||||||
|
r.Post("/budget/categories", SetCategoryBudget)
|
||||||
|
|
||||||
|
// expenses
|
||||||
|
r.Get("/expenses/month", GetMonthExpenses)
|
||||||
|
|
||||||
|
// income
|
||||||
|
r.Get("/income/month", GetMonthIncome)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
@ -0,0 +1,248 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||||
|
"github.com/jacobmveber-01839764/BudgetBuddy/money"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RecentTransactionAmount = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecentTransactionsResponse struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Transactions []db.Transaction `json:"transactions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRecentTransactions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var transactionA []db.Transaction
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
j := 0
|
||||||
|
for i+j < RecentTransactionAmount {
|
||||||
|
// if we are out of transactions, return
|
||||||
|
if i >= len(user.Expenses) && j >= len(user.Income) {
|
||||||
|
break
|
||||||
|
} else if i > len(user.Expenses)-1 { // if we are out of expenses, just use income
|
||||||
|
transactionA = append(transactionA, user.Income[j])
|
||||||
|
j++
|
||||||
|
} else if j > len(user.Income)-1 { // if we are out of income, just use expenses
|
||||||
|
transactionA = append(transactionA, user.Expenses[i])
|
||||||
|
i++
|
||||||
|
} else if user.Expenses[i].Timestamp > user.Income[j].Timestamp {
|
||||||
|
transactionA = append(transactionA, user.Expenses[i])
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
transactionA = append(transactionA, user.Income[j])
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := RecentTransactionsResponse{
|
||||||
|
Status: 200,
|
||||||
|
Transactions: transactionA,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"problem marshalling response\"}")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransaction(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
whole, err := strconv.Atoi(r.Form.Get("whole"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect whole value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decimal, err := strconv.Atoi(r.Form.Get("decimal"))
|
||||||
|
if err != nil || decimal < 0 || decimal > 99 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect decimal value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var cat string
|
||||||
|
|
||||||
|
if r.FormValue("category") == "" {
|
||||||
|
cat = "uncategorized"
|
||||||
|
} else {
|
||||||
|
cat = r.FormValue("category")
|
||||||
|
}
|
||||||
|
|
||||||
|
newT := db.Transaction{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Category: cat,
|
||||||
|
Amount: money.Money{
|
||||||
|
Currency: money.Currency(r.FormValue("currency")),
|
||||||
|
Whole: whole,
|
||||||
|
Decimal: decimal,
|
||||||
|
},
|
||||||
|
Type: r.FormValue("type"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var newArr []db.Transaction
|
||||||
|
var success bool
|
||||||
|
if r.FormValue("type") == "income" {
|
||||||
|
newArr = append(user.Income, newT)
|
||||||
|
success = addToBalance(user, newT.Amount)
|
||||||
|
} else if r.FormValue("type") == "expenses" {
|
||||||
|
newArr = append(user.Expenses, newT)
|
||||||
|
success = subtractFromBalance(user, newT.Amount)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid transaction type - only income or expenses are allowed\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"unable to update balance\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// push the new transaction to db
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: user.Session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: r.FormValue("type"), Value: newArr}}}}
|
||||||
|
userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
|
||||||
|
w.Write([]byte("{\"status\": 200}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecurring(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get session key from request
|
||||||
|
session := r.Header.Get("x-session-key")
|
||||||
|
|
||||||
|
// get collection handle from db
|
||||||
|
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||||
|
|
||||||
|
var user = db.UserSchema{}
|
||||||
|
|
||||||
|
err := userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "session", Value: session}}).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid session key\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
whole, err := strconv.Atoi(r.Form.Get("whole"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect whole value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decimal, err := strconv.Atoi(r.Form.Get("decimal"))
|
||||||
|
if err != nil || decimal < 0 || decimal > 99 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"incorrect decimal value\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
period, err := strconv.Atoi(r.Form.Get("period"))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"period must be specified\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var cat string
|
||||||
|
|
||||||
|
if r.FormValue("category") == "" {
|
||||||
|
cat = "uncategorized"
|
||||||
|
}
|
||||||
|
|
||||||
|
newT := db.Transaction{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Category: cat,
|
||||||
|
Amount: money.Money{
|
||||||
|
Currency: money.Currency(r.FormValue("currency")),
|
||||||
|
Whole: whole,
|
||||||
|
Decimal: decimal,
|
||||||
|
},
|
||||||
|
Type: r.FormValue("type"),
|
||||||
|
}
|
||||||
|
|
||||||
|
newR := db.RecurringTransaction{
|
||||||
|
Transaction: newT,
|
||||||
|
Period: period,
|
||||||
|
Since: time.Now().Unix(),
|
||||||
|
Until: int64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var newArr []db.RecurringTransaction
|
||||||
|
var success bool
|
||||||
|
if r.FormValue("type") == "income" {
|
||||||
|
newArr = append(user.RecurringIncome, newR)
|
||||||
|
success = addToBalance(user, newT.Amount)
|
||||||
|
} else if r.FormValue("type") == "expenses" {
|
||||||
|
newArr = append(user.RecurringExpenses, newR)
|
||||||
|
success = subtractFromBalance(user, newT.Amount)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"invalid transaction type - only income or expenses are allowed\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "{\"error\":\"unable to update balance\"}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// push the new transaction to db
|
||||||
|
filter := bson.D{primitive.E{Key: "session", Value: user.Session}}
|
||||||
|
opts := options.Update().SetUpsert(true)
|
||||||
|
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: r.FormValue("type"), Value: newArr}}}}
|
||||||
|
userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||||
|
|
||||||
|
w.Write([]byte("{\"status\": 200}"))
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in new issue