package handlers import ( "net/http" "strconv" "strings" "time" "github.com/gin-gonic/gin" "go_blog/models" "gorm.io/gorm" ) // userForm holds the posted user fields plus rendering metadata for the shared // create/edit form template. type userForm struct { ID uint Username string Password string DisplayName string Email string Gender string Birthday string // YYYY-MM-DD from the date input Role string Status int IsEdit bool Action string TitleText string } // userListView augments a User with pre-rendered labels/badges so the template // never invokes methods on an interface{}-wrapped struct. type userListView struct { models.User RoleLabel string RoleBadge string StatusLabel string StatusBadge string } // parseUserForm reads the user form fields from the request. func parseUserForm(c *gin.Context) userForm { // Status is always sent from the elements. data["RoleAdmin"] = models.RoleAdmin data["RoleAuthor"] = models.RoleAuthor data["StatusNormal"] = models.StatusNormal data["StatusDisabled"] = models.StatusDisabled data["StatusLocked"] = models.StatusLocked data["StatusUnactivated"] = models.StatusUnactivated data["TrGenderMale"] = tr["gender_male"] data["TrGenderFemale"] = tr["gender_female"] data["TrGenderOther"] = tr["gender_other"] applyUserFormToData(data, f) c.HTML(http.StatusOK, "user_form", data) } // UserListPage renders the admin user management list. func UserListPage(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { tr := getTr(c) var users []models.User db.Order("created_at DESC").Find(&users) views := make([]userListView, 0, len(users)) for _, u := range users { v := userListView{User: u} switch u.Role { case models.RoleAdmin: v.RoleLabel = tr["role_admin"] v.RoleBadge = "bg-purple-100 text-purple-700" default: v.RoleLabel = tr["role_author"] v.RoleBadge = "bg-blue-100 text-blue-700" } switch u.Status { case models.StatusNormal: v.StatusLabel = tr["status_normal"] v.StatusBadge = "bg-green-100 text-green-700" case models.StatusDisabled: v.StatusLabel = tr["status_disabled"] v.StatusBadge = "bg-gray-200 text-gray-600" case models.StatusLocked: v.StatusLabel = tr["status_locked"] v.StatusBadge = "bg-yellow-100 text-yellow-700" default: v.StatusLabel = tr["status_unactivated"] v.StatusBadge = "bg-red-100 text-red-700" } views = append(views, v) } data := DefaultData(c) data["Title"] = tr["admin_users_title"] data["Users"] = views if msg := c.Query("saved"); msg == "1" { switch c.Query("msg") { case "created": data["Success"] = tr["user_created"] case "updated": data["Success"] = tr["user_updated"] case "deleted": data["Success"] = tr["user_deleted"] default: data["Success"] = tr["settings_saved"] } } if e := c.Query("error"); e != "" { switch e { case "self_disable": data["Error"] = tr["user_cannot_disable_self"] case "last_admin": data["Error"] = tr["user_cannot_remove_last_admin"] case "exists": data["Error"] = tr["user_username_exists"] } } c.HTML(http.StatusOK, "user_list", data) } } // UserCreatePage renders the empty user creation form. func UserCreatePage(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { tr := getTr(c) renderUserForm(c, userForm{ Action: "/admin/users/new", TitleText: tr["user_create_title"], Role: models.RoleAuthor, Status: models.StatusNormal, IsEdit: false, }, "") } } // UserCreate handles POST to create a new user. func UserCreate(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { tr := getTr(c) f := parseUserForm(c) f.Action = "/admin/users/new" f.TitleText = tr["user_create_title"] f.IsEdit = false if f.Username == "" { renderUserForm(c, f, tr["user_username_required"]) return } if f.Password == "" { renderUserForm(c, f, tr["user_password_required"]) return } if f.Role == "" { f.Role = models.RoleAuthor } // Username must be unique. var exists int64 db.Model(&models.User{}).Where("username = ?", f.Username).Count(&exists) if exists > 0 { renderUserForm(c, f, tr["user_username_exists"]) return } user := models.User{ Username: f.Username, DisplayName: f.DisplayName, Email: f.Email, Gender: f.Gender, Role: f.Role, Status: f.Status, } if t, err := time.Parse("2006-01-02", f.Birthday); err == nil { user.Birthday = &t } if err := user.SetPassword(f.Password); err != nil { renderUserForm(c, f, tr["article_error"]) return } if err := db.Create(&user).Error; err != nil { renderUserForm(c, f, tr["article_error"]) return } c.Redirect(http.StatusFound, "/admin/users?saved=1&msg=created") } } // UserEditPage renders the user edit form prefilled with an existing user. func UserEditPage(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { tr := getTr(c) id := c.Param("id") var user models.User if err := db.First(&user, id).Error; err != nil { c.Redirect(http.StatusFound, "/admin/users") return } birthday := "" if user.Birthday != nil { birthday = user.Birthday.Format("2006-01-02") } renderUserForm(c, userForm{ ID: user.ID, Username: user.Username, DisplayName: user.DisplayName, Email: user.Email, Gender: user.Gender, Birthday: birthday, Role: user.Role, Status: user.Status, IsEdit: true, Action: "/admin/users/" + id + "/edit", TitleText: tr["user_edit_title"], }, "") } } // UserUpdate handles POST to update an existing user. func UserUpdate(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { tr := getTr(c) id := c.Param("id") f := parseUserForm(c) f.ID = uintFormID(id) f.IsEdit = true f.Action = "/admin/users/" + id + "/edit" f.TitleText = tr["user_edit_title"] var user models.User if err := db.First(&user, id).Error; err != nil { c.Redirect(http.StatusFound, "/admin/users") return } // Keep the original username (it is the login key + article FK source). f.Username = user.Username currentID := userIDFromSession(c) isSelf := user.ID == currentID // Self-protection: cannot disable/lock your own account. if isSelf && f.Status != models.StatusNormal { f.DisplayName = user.DisplayName f.Email = user.Email f.Gender = user.Gender f.Role = user.Role f.Status = user.Status if user.Birthday != nil { f.Birthday = user.Birthday.Format("2006-01-02") } renderUserForm(c, f, tr["user_cannot_disable_self"]) return } // Self-protection: cannot demote the last remaining admin. if user.Role == models.RoleAdmin && f.Role != models.RoleAdmin { var adminCount int64 db.Model(&models.User{}).Where("role = ?", models.RoleAdmin).Count(&adminCount) if adminCount <= 1 { c.Redirect(http.StatusFound, "/admin/users?error=last_admin") return } } if f.Role == "" { f.Role = user.Role } user.DisplayName = f.DisplayName user.Email = f.Email user.Gender = f.Gender user.Role = f.Role user.Status = f.Status if t, err := time.Parse("2006-01-02", f.Birthday); err == nil { user.Birthday = &t } else { user.Birthday = nil } // Optional password reset (leave blank to keep current). if f.Password != "" { if err := user.SetPassword(f.Password); err != nil { renderUserForm(c, f, tr["article_error"]) return } } if err := db.Save(&user).Error; err != nil { renderUserForm(c, f, tr["article_error"]) return } c.Redirect(http.StatusFound, "/admin/users?saved=1&msg=updated") } } // UserDelete soft-deletes a user, with self-protection and last-admin guards. func UserDelete(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") targetID := uintFormID(id) currentID := userIDFromSession(c) var user models.User if err := db.First(&user, id).Error; err != nil { c.Redirect(http.StatusFound, "/admin/users") return } // Cannot delete yourself. if targetID == currentID { c.Redirect(http.StatusFound, "/admin/users?error=self_disable") return } // Cannot delete the last admin. if user.Role == models.RoleAdmin { var adminCount int64 db.Model(&models.User{}).Where("role = ?", models.RoleAdmin).Count(&adminCount) if adminCount <= 1 { c.Redirect(http.StatusFound, "/admin/users?error=last_admin") return } } db.Delete(&user) c.Redirect(http.StatusFound, "/admin/users?saved=1&msg=deleted") } }