diff --git a/internal/biz/user.go b/internal/biz/user.go index 8ddb43c0..3432ac24 100644 --- a/internal/biz/user.go +++ b/internal/biz/user.go @@ -24,6 +24,7 @@ type UserRepo interface { List(page, limit uint) ([]*User, int64, error) Get(id uint) (*User, error) Create(username, password, email string) (*User, error) + UpdateUsername(id uint, username string) error UpdatePassword(id uint, password string) error UpdateEmail(id uint, email string) error Delete(id uint) error diff --git a/internal/data/user.go b/internal/data/user.go index ba5bf9a8..a60d90b8 100644 --- a/internal/data/user.go +++ b/internal/data/user.go @@ -62,6 +62,16 @@ func (r *userRepo) Create(username, password, email string) (*biz.User, error) { return user, nil } +func (r *userRepo) UpdateUsername(id uint, username string) error { + user, err := r.Get(id) + if err != nil { + return err + } + + user.Username = username + return r.db.Save(user).Error +} + func (r *userRepo) UpdatePassword(id uint, password string) error { value, err := r.hasher.Make(password) if err != nil { diff --git a/internal/data/website.go b/internal/data/website.go index 56bb7894..beeda131 100644 --- a/internal/data/website.go +++ b/internal/data/website.go @@ -227,7 +227,7 @@ func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) { for _, website := range websites { crt, _ := io.Read(filepath.Join(app.Root, "server/vhost/cert", website.Name+".pem")) if decode, err := cert.ParseCert(crt); err == nil { - hours := decode.NotAfter.Sub(time.Now()).Hours() + hours := time.Until(decode.NotAfter).Hours() website.CertExpire = fmt.Sprintf("%.2f", hours/24) } } diff --git a/internal/http/request/user.go b/internal/http/request/user.go index 1b2347b5..d27971bf 100644 --- a/internal/http/request/user.go +++ b/internal/http/request/user.go @@ -21,6 +21,11 @@ type UserCreate struct { Email string `json:"email" validate:"required|email"` } +type UserUpdateUsername struct { + ID uint `json:"id" validate:"required|exists:users,id"` + Username string `json:"username" validate:"required|notExists:users,username"` +} + type UserUpdatePassword struct { ID uint `json:"id" validate:"required|exists:users,id"` Password string `json:"password" validate:"required|password"` diff --git a/internal/route/http.go b/internal/route/http.go index 23c2abff..dc533b7c 100644 --- a/internal/route/http.go +++ b/internal/route/http.go @@ -127,6 +127,7 @@ func (route *Http) Register(r *chi.Mux) { r.Route("/users", func(r chi.Router) { r.Get("/", route.user.List) r.Post("/", route.user.Create) + r.Post("/{id}/username", route.user.UpdateUsername) r.Post("/{id}/password", route.user.UpdatePassword) r.Post("/{id}/email", route.user.UpdateEmail) r.Get("/{id}/2fa", route.user.GenerateTwoFA) diff --git a/internal/service/user.go b/internal/service/user.go index 81a4a7b2..53c89dea 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -208,6 +208,21 @@ func (s *UserService) Create(w http.ResponseWriter, r *http.Request) { Success(w, user) } +func (s *UserService) UpdateUsername(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.UserUpdateUsername](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + if err = s.userRepo.UpdateUsername(req.ID, req.Username); err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + Success(w, nil) +} + func (s *UserService) UpdatePassword(w http.ResponseWriter, r *http.Request) { req, err := Bind[request.UserUpdatePassword](r) if err != nil { diff --git a/web/src/api/panel/user/index.ts b/web/src/api/panel/user/index.ts index 9291bd14..85466234 100644 --- a/web/src/api/panel/user/index.ts +++ b/web/src/api/panel/user/index.ts @@ -26,6 +26,9 @@ export default { http.Post('/users', { username, password, email }), // 删除用户 delete: (id: number): any => http.Delete(`/users/${id}`), + // 更新用户用户名 + updateUsername: (id: number, username: string): any => + http.Post(`/users/${id}/username`, { username }), // 更新用户邮箱 updateEmail: (id: number, email: string): any => http.Post(`/users/${id}/email`, { email }), // 更新用户密码 diff --git a/web/src/views/setting/SettingUser.vue b/web/src/views/setting/SettingUser.vue index 4d6e3d37..dd1c1258 100644 --- a/web/src/views/setting/SettingUser.vue +++ b/web/src/views/setting/SettingUser.vue @@ -20,7 +20,17 @@ const columns: any = [ key: 'username', minWidth: 100, resizable: true, - ellipsis: { tooltip: true } + ellipsis: { tooltip: true }, + render(row: any) { + return h(NInput, { + size: 'small', + value: row.username, + onBlur: () => handleUsername(row), + onUpdateValue(v) { + row.username = v + } + }) + } }, { title: $gettext('Email'), @@ -151,6 +161,12 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati } ) +const handleUsername = (row: any) => { + useRequest(user.updateUsername(row.id, row.username)).onSuccess(() => { + window.$message.success($gettext('Modified successfully')) + }) +} + const handleEmail = (row: any) => { useRequest(user.updateEmail(row.id, row.email)).onSuccess(() => { window.$message.success($gettext('Modified successfully'))