diff --git a/handler/account.go b/handler/account.go index 65e4cad..a4d3b42 100644 --- a/handler/account.go +++ b/handler/account.go @@ -7,6 +7,9 @@ import ( "spend-sparrow/utils" "net/http" + + "github.com/a-h/templ" + "github.com/google/uuid" ) type Account interface { @@ -28,8 +31,9 @@ func NewAccount(service service.Account, auth service.Auth, render *Render) Acco } func (handler AccountImpl) Handle(router *http.ServeMux) { - router.Handle("/account", handler.handleAccountPage()) - // router.Handle("POST /account", handler.handleAddAccount()) + router.Handle("GET /account", handler.handleAccountPage()) + router.Handle("GET /account/{id}", handler.handleAccountItemComp()) + router.Handle("POST /account/{id}", handler.handleUpdateAccount()) // router.Handle("GET /account", handler.handleGetAccount()) // router.Handle("DELETE /account/{id}", handler.handleDeleteAccount()) } @@ -42,38 +46,58 @@ func (handler AccountImpl) handleAccountPage() http.HandlerFunc { return } - comp := account.Account() + accounts, err := handler.service.GetAll(user) + if err != nil { + utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + return + } + + comp := account.Account(accounts) handler.render.RenderLayout(r, w, comp, user) } } -// func (handler AccountImpl) handleAddAccount() http.HandlerFunc { -// return func(w http.ResponseWriter, r *http.Request) { -// user := middleware.GetUser(r) -// if user == nil { -// utils.DoRedirect(w, r, "/auth/signin") -// return -// } -// -// var dateStr = r.FormValue("date") -// var typeStr = r.FormValue("type") -// var setsStr = r.FormValue("sets") -// var repsStr = r.FormValue("reps") -// -// wo := service.NewAccountDto("", dateStr, typeStr, setsStr, repsStr) -// wo, err := handler.service.AddAccount(user, wo) -// if err != nil { -// utils.TriggerToast(w, r, "error", "Invalid input values", http.StatusBadRequest) -// http.Error(w, "Invalid input values", http.StatusBadRequest) -// return -// } -// wor := account.Account{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps} -// -// comp := account.AccountItemComp(wor, true) -// handler.render.Render(r, w, comp) -// } -// } -// +func (handler AccountImpl) handleAccountItemComp() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user := middleware.GetUser(r) + if user == nil { + utils.DoRedirect(w, r, "/auth/signin") + return + } + id, err := uuid.Parse(r.PathValue("id")) + if err != nil { + utils.TriggerToastWithStatus(w, r, "error", "Could not parse Id", http.StatusBadRequest) + return + } + + accounts, err := handler.service.Get(user, id) + if err != nil { + utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + return + } + + var comp templ.Component + if r.URL.Query().Get("edit") == "true" { + comp = account.EditAccount(accounts) + } else { + comp = account.AccountItem(accounts) + } + handler.render.Render(r, w, comp) + } +} + +func (handler AccountImpl) handleUpdateAccount() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user := middleware.GetUser(r) + if user == nil { + utils.DoRedirect(w, r, "/auth/signin") + return + } + + utils.TriggerToastWithStatus(w, r, "error", "Account not yet updated", http.StatusBadRequest) + } +} + // func (handler AccountImpl) handleGetAccount() http.HandlerFunc { // return func(w http.ResponseWriter, r *http.Request) { // user := middleware.GetUser(r) diff --git a/handler/auth.go b/handler/auth.go index 962002d..ca0f9e1 100644 --- a/handler/auth.go +++ b/handler/auth.go @@ -96,9 +96,9 @@ func (handler AuthImpl) handleSignIn() http.HandlerFunc { if err != nil { if err == service.ErrInvalidCredentials { - utils.TriggerToast(w, r, "error", "Invalid email or password", http.StatusUnauthorized) + utils.TriggerToastWithStatus(w, r, "error", "Invalid email or password", http.StatusUnauthorized) } else { - utils.TriggerToast(w, r, "error", "An error occurred", http.StatusInternalServerError) + utils.TriggerToastWithStatus(w, r, "error", "An error occurred", http.StatusInternalServerError) } return } @@ -204,19 +204,19 @@ func (handler AuthImpl) handleSignUp() http.HandlerFunc { if err != nil { if errors.Is(err, types.ErrInternal) { - utils.TriggerToast(w, r, "error", "An error occurred", http.StatusInternalServerError) + utils.TriggerToastWithStatus(w, r, "error", "An error occurred", http.StatusInternalServerError) return } else if errors.Is(err, service.ErrInvalidEmail) { - utils.TriggerToast(w, r, "error", "The email provided is invalid", http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", "The email provided is invalid", http.StatusBadRequest) return } else if errors.Is(err, service.ErrInvalidPassword) { - utils.TriggerToast(w, r, "error", service.ErrInvalidPassword.Error(), http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", service.ErrInvalidPassword.Error(), http.StatusBadRequest) return } // If err is "service.ErrAccountExists", then just continue } - utils.TriggerToast(w, r, "success", "An activation link has been send to your email", http.StatusOK) + utils.TriggerToastWithStatus(w, r, "success", "An activation link has been send to your email", http.StatusOK) } } @@ -273,9 +273,9 @@ func (handler AuthImpl) handleDeleteAccountComp() http.HandlerFunc { err := handler.service.DeleteAccount(user, password) if err != nil { if err == service.ErrInvalidCredentials { - utils.TriggerToast(w, r, "error", "Password not correct", http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", "Password not correct", http.StatusBadRequest) } else { - utils.TriggerToast(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) } return } @@ -307,7 +307,7 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc { session := middleware.GetSession(r) user := middleware.GetUser(r) if session == nil || user == nil { - utils.TriggerToast(w, r, "error", "Unathorized", http.StatusUnauthorized) + utils.TriggerToastWithStatus(w, r, "error", "Unathorized", http.StatusUnauthorized) return } @@ -316,11 +316,11 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc { err := handler.service.ChangePassword(user, session.Id, currPass, newPass) if err != nil { - utils.TriggerToast(w, r, "error", "Password not correct", http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", "Password not correct", http.StatusBadRequest) return } - utils.TriggerToast(w, r, "success", "Password changed", http.StatusOK) + utils.TriggerToastWithStatus(w, r, "success", "Password changed", http.StatusOK) } } @@ -343,7 +343,7 @@ func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc { email := r.FormValue("email") if email == "" { - utils.TriggerToast(w, r, "error", "Please enter an email", http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", "Please enter an email", http.StatusBadRequest) return } @@ -353,9 +353,9 @@ func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc { }) if err != nil { - utils.TriggerToast(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) } else { - utils.TriggerToast(w, r, "info", "If the address exists, an email has been sent.", http.StatusOK) + utils.TriggerToastWithStatus(w, r, "info", "If the address exists, an email has been sent.", http.StatusOK) } } } @@ -365,7 +365,7 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc { pageUrl, err := url.Parse(r.Header.Get("HX-Current-URL")) if err != nil { log.Error("Could not get current URL: %v", err) - utils.TriggerToast(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) return } @@ -374,9 +374,9 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc { err = handler.service.ForgotPassword(token, newPass) if err != nil { - utils.TriggerToast(w, r, "error", err.Error(), http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", err.Error(), http.StatusBadRequest) } else { - utils.TriggerToast(w, r, "success", "Password changed", http.StatusOK) + utils.TriggerToastWithStatus(w, r, "success", "Password changed", http.StatusOK) } } } diff --git a/handler/middleware/cross_site_request_forgery.go b/handler/middleware/cross_site_request_forgery.go index 1c0da74..663a2fc 100644 --- a/handler/middleware/cross_site_request_forgery.go +++ b/handler/middleware/cross_site_request_forgery.go @@ -59,7 +59,7 @@ func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler if session == nil || csrfToken == "" || !auth.IsCsrfTokenValid(csrfToken, session.Id) { log.Info("CSRF-Token not correct") if r.Header.Get("HX-Request") == "true" { - utils.TriggerToast(w, r, "error", "CSRF-Token not correct", http.StatusBadRequest) + utils.TriggerToastWithStatus(w, r, "error", "CSRF-Token not correct", http.StatusBadRequest) } else { http.Error(w, "CSRF-Token not correct", http.StatusBadRequest) } diff --git a/handler/middleware/gzip.go b/handler/middleware/gzip.go new file mode 100644 index 0000000..1def7fd --- /dev/null +++ b/handler/middleware/gzip.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "compress/gzip" + "io" + "net/http" + "strings" +) + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (w gzipResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func Gzip(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + next.ServeHTTP(w, r) + return + } + + w.Header().Set("Content-Encoding", "gzip") + gz := gzip.NewWriter(w) + defer gz.Close() + gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w} + next.ServeHTTP(gzr, r) + }) +} diff --git a/handler/middleware/wrapper.go b/handler/middleware/wrapper.go index 80dcb20..cd5f9af 100644 --- a/handler/middleware/wrapper.go +++ b/handler/middleware/wrapper.go @@ -2,10 +2,11 @@ package middleware import "net/http" +// Chain list of handlers together func Wrapper(next http.Handler, handlers ...func(http.Handler) http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { lastHandler := next - for i := len(handlers) - 1; i >= 0; i-- { + for i := 0; i < len(handlers); i++ { lastHandler = handlers[i](lastHandler) } lastHandler.ServeHTTP(w, r) diff --git a/main.go b/main.go index e4e6321..4f271ed 100644 --- a/main.go +++ b/main.go @@ -130,10 +130,12 @@ func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler { return middleware.Wrapper( router, - middleware.Log, - middleware.CacheControl, middleware.SecurityHeaders(serverSettings), - middleware.Authenticate(authService), + middleware.CacheControl, middleware.CrossSiteRequestForgery(authService), + middleware.Authenticate(authService), + middleware.Log, + // Gzip last, as it compresses the body + middleware.Gzip, ) } diff --git a/service/account.go b/service/account.go index 5e1223e..41c32ff 100644 --- a/service/account.go +++ b/service/account.go @@ -18,7 +18,8 @@ var ( type Account interface { Add(user *types.User, name string) (*types.Account, error) Update(user *types.User, id uuid.UUID, name string) (*types.Account, error) - Get(user *types.User) ([]*types.Account, error) + Get(user *types.User, id uuid.UUID) (*types.Account, error) + GetAll(user *types.User) ([]*types.Account, error) Delete(user *types.User, id uuid.UUID) error } @@ -111,13 +112,27 @@ func (service AccountImpl) Update(user *types.User, id uuid.UUID, name string) ( return account, nil } -func (service AccountImpl) Get(user *types.User) ([]*types.Account, error) { +func (service AccountImpl) Get(user *types.User, id uuid.UUID) (*types.Account, error) { if user == nil { return nil, types.ErrInternal } - accounts, err := service.db.GetAll(user.GroupId) + account, err := service.db.Get(user.Id, id) + if err != nil { + return nil, types.ErrInternal + } + + return account, nil +} + +func (service AccountImpl) GetAll(user *types.User) ([]*types.Account, error) { + + if user == nil { + return nil, types.ErrInternal + } + + accounts, err := service.db.GetAll(user.Id) if err != nil { return nil, types.ErrInternal } diff --git a/static/js/toast.js b/static/js/toast.js index 2f92b18..ac624ba 100644 --- a/static/js/toast.js +++ b/static/js/toast.js @@ -1,5 +1,4 @@ - function getClass(type) { switch (type) { case "error": diff --git a/template/account/account.templ b/template/account/account.templ index 03858f6..8713209 100644 --- a/template/account/account.templ +++ b/template/account/account.templ @@ -2,48 +2,86 @@ package account import "fmt" import "spend-sparrow/template/svg" +import "spend-sparrow/types" -templ Account() { +templ Account(accounts []*types.Account) {
{ account.Name }
+{ displayBalance(account.CurrentBalance) }
+ + + + + + + + +{ name }
-{ displayBalance(balance) }
- - - -