Dima 1 рік тому
батько
коміт
015b7c6417

+ 1 - 0
internal/fiber/fiber.go

@@ -68,6 +68,7 @@ func Init(sp interfaces.ServiceProvider) (*fiber.App, error) {
 	}), adminHandler.LoginHandler(sp))
 
 	admin.All("/logout", adminHandler.LogoutHandler(sp))
+	admin.All("/user/change-password", adminHandler.ChangePassword(sp))
 	admin.All("/article/add", adminHandler.AddArticleHandler(sp))
 	admin.All("/article/edit/:id<int>", adminHandler.EditArticleHandler(sp))
 	admin.All("/article/delete/:id<int>", adminHandler.DeleteArticleHandler(sp))

+ 1 - 0
internal/interfaces/user.go

@@ -9,4 +9,5 @@ import (
 type UserRepository interface {
 	Get(ctx context.Context, username string) (*models.User, error)
 	Add(ctx context.Context, username string, password string) (int, error)
+	UpdatePassword(ctx context.Context, id int, newPassword string) error
 }

+ 5 - 0
internal/models/user.go

@@ -11,3 +11,8 @@ type User struct {
 	CreatedAt time.Time
 	UpdatedAt time.Time
 }
+
+type ChangePasswordForm struct {
+	OldPassword string `form:"old_password" validate:"required"`
+	NewPassword string `form:"new_password" validate:"required"`
+}

+ 22 - 4
internal/repositories/user.go

@@ -25,7 +25,7 @@ func InitUserRepository(db *sql.DB) interfaces.UserRepository {
 	return userRepository{db: db}
 }
 
-func (r userRepository) Get(ctx context.Context, username string) (*models.User, error) {
+func (u userRepository) Get(ctx context.Context, username string) (*models.User, error) {
 	query, args, err := sq.Select("id", "username", "password", "created_at", "updated_at").
 		From(userTableName).
 		PlaceholderFormat(sq.Dollar).
@@ -37,7 +37,7 @@ func (r userRepository) Get(ctx context.Context, username string) (*models.User,
 	}
 
 	var res models.User
-	err = r.db.QueryRowContext(ctx, query, args...).
+	err = u.db.QueryRowContext(ctx, query, args...).
 		Scan(&res.ID, &res.Username, &res.Password, &res.CreatedAt, &res.UpdatedAt)
 
 	if err != nil {
@@ -47,7 +47,7 @@ func (r userRepository) Get(ctx context.Context, username string) (*models.User,
 	return &res, nil
 }
 
-func (r userRepository) Add(ctx context.Context, username string, password string) (int, error) {
+func (u userRepository) Add(ctx context.Context, username string, password string) (int, error) {
 	query, args, err := sq.Insert(userTableName).
 		PlaceholderFormat(sq.Dollar).
 		Columns("username", "password").
@@ -60,9 +60,27 @@ func (r userRepository) Add(ctx context.Context, username string, password strin
 	}
 
 	var id int
-	if err = r.db.QueryRowContext(ctx, query, args...).Scan(&id); err != nil {
+	if err = u.db.QueryRowContext(ctx, query, args...).Scan(&id); err != nil {
 		return 0, err
 	}
 
 	return id, nil
 }
+
+func (u userRepository) UpdatePassword(ctx context.Context, id int, newPassword string) error {
+	query, args, err := sq.Update(userTableName).
+		PlaceholderFormat(sq.Dollar).
+		Set("password", newPassword).
+		Set("updated_at", "NOW()").
+		Where(sq.Eq{"id": id}).
+		ToSql()
+
+	if err != nil {
+		return err
+	}
+
+	_, err = u.db.ExecContext(ctx, query, args...)
+
+	return err
+
+}

+ 67 - 0
internal/services/handler/admin/user.go

@@ -0,0 +1,67 @@
+package admin
+
+import (
+	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helpers"
+	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/interfaces"
+	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/models"
+	"github.com/go-playground/validator/v10"
+	"github.com/gofiber/fiber/v2"
+)
+
+const errIncorrectOldPassword = "Неверный старый пароль"
+
+func ChangePassword(sp interfaces.ServiceProvider) fiber.Handler {
+	return func(fctx *fiber.Ctx) error {
+		ctx := fctx.Context()
+		var validate = validator.New()
+		validateErrors := make(map[string]string)
+
+		trans, err := helpers.GetDefaultTranslator(validate)
+		if err != nil {
+			return err
+		}
+
+		form := models.ChangePasswordForm{}
+
+		if fctx.Method() == fiber.MethodPost {
+			if err = fctx.BodyParser(&form); err != nil {
+				return err
+			}
+
+			if err = validate.Struct(form); err != nil {
+				validateErrors = helpers.FormatValidateErrors(err, trans)
+			}
+
+			if len(validateErrors) == 0 {
+				claims := sp.GetAuthService().GetClaims(fctx)
+
+				user, err := sp.GetUserRepository().Get(ctx, claims["name"].(string))
+				if err != nil {
+					return err
+				}
+
+				if sp.GetAuthService().IsCorrectPassword(form.OldPassword, user.Password) {
+					newPassword, err := sp.GetAuthService().GeneratePasswordHash(form.NewPassword)
+					if err != nil {
+						return err
+					}
+
+					if err = sp.GetUserRepository().UpdatePassword(ctx, user.ID, newPassword); err != nil {
+						return err
+					}
+
+					return fctx.Redirect("/admin")
+				}
+
+				validateErrors["ChangePasswordForm.OldPassword"] = errIncorrectOldPassword
+			}
+		}
+
+		return fctx.Render("admin/user_change_password", fiber.Map{
+			"form":    form,
+			"errors":  validateErrors,
+			"section": "change_password",
+			"title":   "Изменение пароля",
+		}, "admin/_layout")
+	}
+}

+ 6 - 1
internal/templates/admin/_layout.html

@@ -1,4 +1,4 @@
-{{ $v := version }}{{ $sTag := "tag" }}{{ $sArticle := "article" }}<!doctype html>
+{{ $v := version }}{{ $sTag := "tag" }}{{ $sArticle := "article" }}{{ $sPass := "change_password" }}<!doctype html>
 <html>
 <head>
     <meta charset="utf-8">
@@ -25,6 +25,11 @@
                         </a>
                     </li>
                     <li class="nav-item mt-3 pt-3 border-top">
+                        <a class="nav-link {{ if eq .section $sPass }}active{{ end }}" href="/admin/user/change-password">
+                            Изменить пароль
+                        </a>
+                    </li>
+                    <li class="nav-item">
                         <a class="nav-link" href="/admin/logout">
                             Выход
                         </a>

+ 33 - 0
internal/templates/admin/user_change_password.html

@@ -0,0 +1,33 @@
+<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 border-bottom">
+        <h3 class="h3">{{ .title }}</h3>
+    </div>
+    <form method="post">
+        <div class="row mb-3 mt-3">
+            <label class="col-sm-2 col-form-label col-form-label-sm">Старый пароль</label>
+            <div class="col-sm-10">
+                {{ $tagErr := index .errors "ChangePasswordForm.OldPassword" }}
+                <input type="text" class="form-control form-control-sm {{ if $tagErr }}is-invalid{{ end }}" name="old_password" value="{{ .form.OldPassword }}">
+                {{ if $tagErr }}
+                <div class="invalid-feedback">
+                    {{ $tagErr }}
+                </div>
+                {{ end }}
+            </div>
+        </div>
+        <div class="row mb-3 border-bottom pb-3">
+            <label class="col-sm-2 col-form-label col-form-label-sm">Новый пароль</label>
+            <div class="col-sm-10">
+                {{ $urlErr := index .errors "ChangePasswordForm.NewPassword" }}
+                <input type="text" class="form-control form-control-sm {{ if $urlErr }}is-invalid{{ end }}" name="new_password" value="{{ .form.NewPassword }}">
+                {{ if $urlErr }}
+                <div class="invalid-feedback">
+                    {{ $urlErr }}
+                </div>
+                {{ end }}
+            </div>
+        </div>
+        <a href="/admin" class="btn btn-sm btn-secondary">Назад</a>
+        <button type="submit" class="btn btn-sm btn-primary">Сохранить</button>
+    </form>
+</main>