153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"trade/web/internal/auth"
|
|
"trade/web/internal/middleware"
|
|
"trade/web/internal/store"
|
|
)
|
|
|
|
type createUserReq struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Role string `json:"role"`
|
|
}
|
|
|
|
type patchUserReq struct {
|
|
Password *string `json:"password,omitempty"`
|
|
Disabled *bool `json:"disabled,omitempty"`
|
|
}
|
|
|
|
func (d *Deps) AdminListUsers(w http.ResponseWriter, r *http.Request) {
|
|
users, err := d.Auth.ListUsers()
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
out := make([]map[string]any, 0, len(users))
|
|
for i := range users {
|
|
out = append(out, sanitize(&users[i]))
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|
|
|
|
func (d *Deps) AdminCreateUser(w http.ResponseWriter, r *http.Request) {
|
|
var req createUserReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeErr(w, http.StatusBadRequest, "invalid json")
|
|
return
|
|
}
|
|
req.Username = strings.TrimSpace(req.Username)
|
|
if req.Username == "" || len(req.Password) < 6 {
|
|
writeErr(w, http.StatusBadRequest, "用户名必填,密码至少 6 位")
|
|
return
|
|
}
|
|
role := strings.TrimSpace(req.Role)
|
|
if role == "" {
|
|
role = store.RoleUser
|
|
}
|
|
if role != store.RoleAdmin && role != store.RoleUser {
|
|
writeErr(w, http.StatusBadRequest, "role 取值必须是 admin 或 user")
|
|
return
|
|
}
|
|
hash, err := auth.HashPassword(req.Password)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "hash failed")
|
|
return
|
|
}
|
|
u, err := d.Auth.CreateUser(req.Username, hash, role)
|
|
if err != nil {
|
|
// UNIQUE 冲突等
|
|
writeErr(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, sanitize(u))
|
|
}
|
|
|
|
func (d *Deps) AdminPatchUser(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
|
if err != nil {
|
|
writeErr(w, http.StatusBadRequest, "invalid id")
|
|
return
|
|
}
|
|
var req patchUserReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeErr(w, http.StatusBadRequest, "invalid json")
|
|
return
|
|
}
|
|
if req.Password == nil && req.Disabled == nil {
|
|
writeErr(w, http.StatusBadRequest, "无可更新字段")
|
|
return
|
|
}
|
|
if req.Password != nil {
|
|
if len(*req.Password) < 6 {
|
|
writeErr(w, http.StatusBadRequest, "新密码至少 6 位")
|
|
return
|
|
}
|
|
hash, err := auth.HashPassword(*req.Password)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, "hash failed")
|
|
return
|
|
}
|
|
if err := d.Auth.UpdatePassword(id, hash); err != nil {
|
|
writeErr(w, statusForErr(err), err.Error())
|
|
return
|
|
}
|
|
// 管理员重置密码后,强制用户下次登录改密
|
|
if err := d.Auth.SetForcePasswordChange(id, true); err != nil {
|
|
writeErr(w, statusForErr(err), err.Error())
|
|
return
|
|
}
|
|
}
|
|
if req.Disabled != nil {
|
|
// 禁止禁用自己,避免管理员锁死自己
|
|
me, _ := middleware.FromContext(r.Context())
|
|
if *req.Disabled && me.ID == id {
|
|
writeErr(w, http.StatusBadRequest, "不能禁用自己")
|
|
return
|
|
}
|
|
if err := d.Auth.SetDisabled(id, *req.Disabled); err != nil {
|
|
writeErr(w, statusForErr(err), err.Error())
|
|
return
|
|
}
|
|
}
|
|
u, err := d.Auth.GetByID(id)
|
|
if err != nil {
|
|
writeErr(w, statusForErr(err), err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, sanitize(u))
|
|
}
|
|
|
|
func (d *Deps) AdminDeleteUser(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
|
if err != nil {
|
|
writeErr(w, http.StatusBadRequest, "invalid id")
|
|
return
|
|
}
|
|
me, _ := middleware.FromContext(r.Context())
|
|
if me.ID == id {
|
|
writeErr(w, http.StatusBadRequest, "不能删除自己")
|
|
return
|
|
}
|
|
if err := d.Auth.DeleteUser(id); err != nil {
|
|
writeErr(w, statusForErr(err), err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func statusForErr(err error) int {
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
return http.StatusNotFound
|
|
}
|
|
return http.StatusInternalServerError
|
|
}
|