From 5f8548958fa0008b4227c6cec0add376eeef4334 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 2 Jun 2023 20:00:14 +0200 Subject: [PATCH] go: add usr updating [wip] --- app/routes.go | 4 +- handlers/manage-user.go | 293 ++++++++++++++++++++++++++++- modules/user/user.go | 4 +- templates/manage/user-details.tmpl | 5 + templates/manage/user-edit.tmpl | 113 +++++++++++ templates/navbar.tmpl | 8 +- 6 files changed, 419 insertions(+), 8 deletions(-) create mode 100644 templates/manage/user-edit.tmpl diff --git a/app/routes.go b/app/routes.go index 740629b..29ca9fc 100644 --- a/app/routes.go +++ b/app/routes.go @@ -58,8 +58,10 @@ func (a *App) SetupRoutes() error { e.GET("/manage/users", handlers.ManageUsers(), handlers.MiddlewareSession) e.GET("/manage/users/new", handlers.ManageUsers(), handlers.MiddlewareSession) - e.GET("/manage/users/:id", handlers.ViewUser(), handlers.MiddlewareSession) e.POST("/manage/users/create", handlers.CreateUser(), handlers.MiddlewareSession) + e.GET("/manage/users/:id", handlers.ViewUser(), handlers.MiddlewareSession) + e.GET("/manage/users/:id/edit", handlers.EditUser(), handlers.MiddlewareSession, handlers.MiddlewareCache, compress) + e.POST("/manage/users/:id/update", handlers.UpdateUser(), handlers.MiddlewareSession) e.GET("/logout", handlers.Logout(), compress) e.POST("/logout", handlers.Logout()) diff --git a/handlers/manage-user.go b/handlers/manage-user.go index 46e3fd1..a6637d9 100644 --- a/handlers/manage-user.go +++ b/handlers/manage-user.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strings" "git.dotya.ml/mirre-mt/pcmt/ent" moduser "git.dotya.ml/mirre-mt/pcmt/modules/user" @@ -306,7 +307,7 @@ func ViewUser() echo.HandlerFunc { uid := new(userID) err := c.Bind(uid) - if err == nil { + if err == nil { //nolint:dupl usr, err := getUserByID(ctx, dbclient, uid.ID) if err != nil { if errors.Is(err, moduser.ErrUserNotFound) { //nolint:gocritic @@ -372,3 +373,293 @@ func ViewUser() echo.HandlerFunc { ) } } + +//nolint:dupl +func EditUser() echo.HandlerFunc { + return func(c echo.Context) error { + addHeaders(c) + + u, ok := c.Get("sessUsr").(moduser.User) + if !ok { + return renderErrorPage( + c, + http.StatusUnauthorized, + http.StatusText(http.StatusUnauthorized), + "username was nil", + ) + } + + if !u.IsAdmin { + c.Logger().Debug("this is a restricted endpoint") + + status := http.StatusUnauthorized + msg := http.StatusText(status) + + return renderErrorPage( + c, status, msg+": You should not be here", "Restricted endpoint", + ) + } + + ctx, ok := c.Get("sloggerCtx").(context.Context) + if !ok { + ctx = context.WithValue(context.Background(), moduser.CtxKey{}, slogger) + } + + p := page{ + AppName: setting.AppName(), + AppVer: appver, + Title: "Manage Users - Edit User", + DevelMode: setting.IsDevel(), + Current: "manage-users-edit-user", + User: u, + } + tmpl := "manage/user-edit.tmpl" + data := make(map[string]any) + id := strings.TrimPrefix(strings.TrimSuffix(c.Request().URL.Path, "/edit"), "/manage/users/") + + usr, err := getUserByID(ctx, dbclient, id) + if err != nil { + //nolint:dupl + switch { + case errors.Is(err, moduser.ErrUserNotFound): + c.Logger().Errorf("user not found by ID: '%s'", id) + + data["flash"] = fmt.Sprintf("No user found with the UUID: %q", id) + p.Data = data + + return c.Render( + http.StatusNotFound, + tmpl, + p, + ) + + case errors.Is(err, moduser.ErrFailedToQueryUser): + c.Logger().Errorf("failed to query user by ID: '%s'", id) + + data["flash"] = fmt.Sprintf("failed to query user by UUID %q", id) + p.Data = data + + return c.Render( + http.StatusInternalServerError, + tmpl, + p, + ) + + case errors.Is(err, moduser.ErrBadUUID): + c.Logger().Errorf("Invalid UUID '%s': %q", id, err) + + data["flash"] = fmt.Sprintf("invalid UUID %q", id) + p.Data = data + + return c.Render( + http.StatusBadRequest, + "manage/user-details.tmpl", + p, + ) + } + + c.Logger().Errorf("UUID-related issue for UUID '%s': %q", id, err) + + return renderErrorPage( + c, + http.StatusInternalServerError, + http.StatusText(http.StatusInternalServerError), + err.Error(), + ) + } + + data["user"] = usr + p.Data = data + + return c.Render( + http.StatusOK, + tmpl, + p, + ) + } +} + +//nolint:dupl +func UpdateUser() echo.HandlerFunc { //nolint:gocognit + return func(c echo.Context) error { + addHeaders(c) + + u, ok := c.Get("sessUsr").(moduser.User) + if !ok { + return renderErrorPage( + c, + http.StatusUnauthorized, + http.StatusText(http.StatusUnauthorized), + "username was nil", + ) + } + + if !u.IsAdmin { + c.Logger().Debug("this is a restricted endpoint") + + status := http.StatusUnauthorized + msg := http.StatusText(status) + + return renderErrorPage( + c, status, msg+": You should not be here", "Restricted endpoint", + ) + } + + ctx, ok := c.Get("sloggerCtx").(context.Context) + if !ok { + ctx = context.WithValue(context.Background(), moduser.CtxKey{}, slogger) + } + + p := page{ + AppName: setting.AppName(), + AppVer: appver, + Title: "Manage Users - Edit User", + DevelMode: setting.IsDevel(), + Current: "manage-users-edit-user", + User: u, + } + tmpl := "manage/user-edit.tmpl" + data := make(map[string]any) + id := strings.TrimPrefix(strings.TrimSuffix(c.Request().URL.Path, "/update"), "/manage/users/") + + usr, err := getUserByID(ctx, dbclient, id) + if err != nil { + switch { + case errors.Is(err, moduser.ErrUserNotFound): + c.Logger().Errorf("user not found by ID: '%s'", id) + + data["flash"] = fmt.Sprintf("No user found with the UUID: %q", id) + p.Data = data + + return c.Render( + http.StatusNotFound, + tmpl, + p, + ) + + case errors.Is(err, moduser.ErrFailedToQueryUser): + c.Logger().Errorf("failed to query user by ID: '%s'", id) + + data["flash"] = fmt.Sprintf("failed to query user by UUID %q", id) + p.Data = data + + return c.Render( + http.StatusInternalServerError, + tmpl, + p, + ) + + case errors.Is(err, moduser.ErrBadUUID): + c.Logger().Errorf("Invalid UUID '%s': %q", id, err) + + data["flash"] = fmt.Sprintf("invalid UUID %q", id) + p.Data = data + + return c.Render( + http.StatusBadRequest, + "manage/user-details.tmpl", + p, + ) + } + + c.Logger().Errorf("UUID-related issue for UUID '%s': %q", id, err) + + return renderErrorPage( + c, + http.StatusInternalServerError, + http.StatusText(http.StatusInternalServerError), + err.Error(), + ) + } + + uu := new(userCreate) + + if err := c.Bind(uu); err != nil { + return renderErrorPage( + c, + http.StatusBadRequest, + http.StatusText(http.StatusBadRequest), + err.Error(), + ) + } + + if uu.Username == "" || uu.Password == "" || uu.RepeatPassword == "" || uu.Password != uu.RepeatPassword { + c.Logger().Error("username or password not set, returning to /manage/users/edit") + + msg := "Error: both username and password need to be set" + + if uu.Password != uu.RepeatPassword { + msg += "; the same password needs to be passed in twice" + } + + data["flash"] = msg + data["form"] = uu + p.Data = data + + return c.Render( + http.StatusBadRequest, + "manage/user-edit.tmpl", + p, + ) + } + + if usr.Username != uu.Username { + exists, err := moduser.UsernameExists(ctx, dbclient, uu.Username) + if err != nil { + return renderErrorPage( + c, + http.StatusInternalServerError, + http.StatusText(http.StatusInternalServerError), + err.Error(), + ) + } + + if exists { + msg := fmt.Sprintf("Error: username %q is already taken", uu.Username) + + c.Logger().Warn(msg) + + data["flash"] = msg + p.Data = data + + return c.Render( + http.StatusBadRequest, + "manage/user-edit.tmpl", + p, + ) + } + } + + if usr.Email != uu.Email { + exists, err := moduser.EmailExists(ctx, dbclient, uu.Email) + if err != nil { + return renderErrorPage( + c, + http.StatusInternalServerError, + http.StatusText(http.StatusInternalServerError), + err.Error(), + ) + } + + if exists { + msg := fmt.Sprintf("Error: email %q is already taken", uu.Email) + + c.Logger().Error(msg) + + return renderErrorPage( + c, + http.StatusBadRequest, + msg, + msg, + ) + } + } + + // now update + + data["user"] = usr + p.Data = data + + return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/manage/users/%s/edit", usr.ID)) + } +} diff --git a/modules/user/user.go b/modules/user/user.go index 99a48fb..d53a6f1 100644 --- a/modules/user/user.go +++ b/modules/user/user.go @@ -153,14 +153,14 @@ func Exists(ctx context.Context, client *ent.Client, username, email string) (bo if err != nil { log.Warn("failed to check whether username is taken", "error", err, "username requested", username) - return false, fmt.Errorf("failed querying user: %w", err) + return false, ErrFailedToQueryUser } emailExists, err := EmailExists(ctx, client, email) if err != nil { log.Warn("failed to check whether user email exists", "error", err, "user email requested", email) - return false, fmt.Errorf("failed querying user: %w", err) + return false, ErrFailedToQueryUser } switch { diff --git a/templates/manage/user-details.tmpl b/templates/manage/user-details.tmpl index cec81bc..d3ecf03 100644 --- a/templates/manage/user-details.tmpl +++ b/templates/manage/user-details.tmpl @@ -12,6 +12,11 @@ {{if and .Data .Data.user -}} +
+ + Edit + +
ID: diff --git a/templates/manage/user-edit.tmpl b/templates/manage/user-edit.tmpl new file mode 100644 index 0000000..a037061 --- /dev/null +++ b/templates/manage/user-edit.tmpl @@ -0,0 +1,113 @@ +{{ template "head.tmpl" . }} + +{{ template "navbar.tmpl" . }} +
+ + + + + + + + + + + + + +
+
+
+
+

+ Edit user +

+ {{ if and .Data .Data.flash }} +

+ {{- .Data.flash -}} +

+ {{- end }} +
+
+ {{if and .Data .Data.user -}} + + {{end}} +
+ +
+ + {{ template "svg-user.tmpl" }} + + +
+
+ + {{ template "svg-email.tmpl" }} + + +

+ Please provide a valid email address. +

+
+
+ + {{ template "svg-password.tmpl" }} + + +
+
+ + {{ template "svg-password.tmpl" }} + + +
+
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+
+
+
+{{ template "footer.tmpl" . }} diff --git a/templates/navbar.tmpl b/templates/navbar.tmpl index 8465c7c..54ad578 100644 --- a/templates/navbar.tmpl +++ b/templates/navbar.tmpl @@ -25,11 +25,11 @@ {{ if and .User .User.IsLoggedIn }}
  • - {{ if or (pageIs .Current "manage-users") (pageIs .Current "manage-users-new") (pageIs .Current "manage-users-user-details") }} + {{- if or (pageIs .Current "manage-users") (pageIs .Current "manage-users-new") (pageIs .Current "manage-users-user-details") (pageIs .Current "manage-users-edit-user") -}} - {{ else }} - - {{ end }} + {{else}} + + {{end}} User management