改进修改用户接口

This commit is contained in:
xmdhs 2023-10-10 18:43:35 +08:00
parent 81c8cee2c4
commit 63be2bc13f
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
10 changed files with 119 additions and 86 deletions

View File

@ -117,7 +117,7 @@ export async function ListUser(page: number, token: string, email: string, name:
export async function editUser(u: EditUser, token: string, uid: string) { export async function editUser(u: EditUser, token: string, uid: string) {
const r = await fetch(import.meta.env.VITE_APIADDR + "/api/v1/admin/user/" + uid, { const r = await fetch(import.meta.env.VITE_APIADDR + "/api/v1/admin/user/" + uid, {
method: "POST", method: "PATCH",
headers: { headers: {
"Authorization": "Bearer " + token "Authorization": "Bearer " + token
}, },

View File

@ -58,10 +58,10 @@ export interface UserInfo {
} }
export interface EditUser { export interface EditUser {
email: string email?: string
name: string name?: string
password: string password?: string
is_admin: boolean is_admin?: boolean
is_disable: boolean is_disable?: boolean
del_textures: boolean del_textures?: boolean
} }

View File

@ -8,7 +8,7 @@ import Paper from '@mui/material/Paper';
import useTitle from '@/hooks/useTitle'; import useTitle from '@/hooks/useTitle';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { ListUser, editUser } from '@/apis/apis'; import { ListUser, editUser } from '@/apis/apis';
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { token } from '@/store/store'; import { token } from '@/store/store';
import TablePagination from '@mui/material/TablePagination'; import TablePagination from '@mui/material/TablePagination';
@ -133,25 +133,50 @@ function MyDialog({ open, row, setOpen, onUpdate }: MyDialogProp) {
const [load, setLoad] = useState(false) const [load, setLoad] = useState(false)
const nowToken = useAtomValue(token) const nowToken = useAtomValue(token)
const [err, setErr] = useState("") const [err, setErr] = useState("")
const editValue = useRef<EditUser>({});
useEffect(() => { useEffect(() => {
if (!row) return
setErow({ setErow({
email: row?.email ?? "", email: row.email,
name: row?.name ?? "", name: row.name,
password: "", password: "",
is_admin: row?.is_admin ?? false, is_admin: row.is_admin,
is_disable: row?.is_disable ?? false, is_disable: row.is_disable,
del_textures: false, del_textures: false,
}) })
}, [row]) editValue.current = {}
}, [row, open])
const handleOpen = () => { const handleOpen = () => {
if (load) return if (load) return
setLoad(true) setLoad(true)
editUser(erow, nowToken, String(row?.uid)).then(() => [setOpen(false), onUpdate()]).finally(() => setLoad(false)). editUser(editValue.current, nowToken, String(row?.uid)).then(() => [setOpen(false), onUpdate(), editValue.current = {}]).finally(() => setLoad(false)).
catch(e => setErr(String(e))) catch(e => setErr(String(e)))
} }
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
function handleSetValue(key: StringKeys<Required<EditUser>>, value: string) {
setErow(produce(v => {
v[key] = value
editValue.current[key] = value
}))
}
type BoolKeys<T> = {
[K in keyof T]: T[K] extends boolean ? K : never;
}[keyof T];
function handleSetChecked(key: BoolKeys<Required<EditUser>>, value: boolean) {
setErow(produce(v => {
v[key] = value
editValue.current[key] = value
}))
}
return (<> return (<>
<Dialog open={open}> <Dialog open={open}>
@ -171,10 +196,7 @@ function MyDialog({ open, row, setOpen, onUpdate }: MyDialogProp) {
type="email" type="email"
variant="standard" variant="standard"
value={erow?.email} value={erow?.email}
onChange={e => setErow(produce(v => { onChange={e => handleSetValue('email', e.target.value)}
v.email = e.target.value
return
}))}
/> />
<TextField <TextField
margin="dense" margin="dense"
@ -182,33 +204,22 @@ function MyDialog({ open, row, setOpen, onUpdate }: MyDialogProp) {
type="text" type="text"
variant="standard" variant="standard"
value={erow?.name} value={erow?.name}
onChange={e => setErow(produce(v => { onChange={e => handleSetValue('name', e.target.value)}
v.name = e.target.value
return
}))}
/> />
<TextField <TextField
margin="dense" margin="dense"
label="密码" label="密码"
type="text" type="text"
placeholder='(未更改)'
variant="standard" variant="standard"
value={erow?.password} value={erow?.password}
onChange={e => setErow(produce(v => { onChange={e => handleSetValue('password', e.target.value)}
v.password = e.target.value
return
}))}
/> />
</Box> </Box>
<FormGroup row sx={{ gridArea: "b" }}> <FormGroup row sx={{ gridArea: "b" }}>
<FormControlLabel control={<Checkbox checked={erow?.is_admin} onChange={e => setErow(produce(v => { <FormControlLabel control={<Checkbox checked={erow?.is_admin} onChange={e => handleSetChecked('is_admin', e.target.checked)} />} label="管理权限" />
v.is_admin = e.target.checked <FormControlLabel control={<Checkbox checked={erow?.is_disable} onChange={e => handleSetChecked('is_disable', e.target.checked)} />} label="禁用" />
}))} />} label="管理权限" /> <FormControlLabel control={<Checkbox checked={erow?.del_textures} onChange={e => handleSetChecked('del_textures', e.target.checked)} />} label="清空材质" />
<FormControlLabel control={<Checkbox checked={erow?.is_disable} onChange={e => setErow(produce(v => {
v.is_disable = e.target.checked
}))} />} label="禁用" />
<FormControlLabel control={<Checkbox checked={erow?.del_textures} onChange={e => setErow(produce(v => {
v.del_textures = e.target.checked
}))} />} label="清空材质" />
</FormGroup> </FormGroup>
<Box sx={{ gridArea: "c" }}> <Box sx={{ gridArea: "c" }}>
<SkinViewUUID uuid={row?.uuid ?? ""} width={175} height={175} /> <SkinViewUUID uuid={row?.uuid ?? ""} width={175} height={175} />

View File

@ -93,7 +93,7 @@ function ChangePasswd() {
required required
error={oldPassErr} error={oldPassErr}
helperText={oldPassErr ? "旧密码错误" : ""} helperText={oldPassErr ? "旧密码错误" : ""}
onChange={p => setPass(produce(v => { v.old = p.target.value; return v }))} onChange={p => setPass(produce(v => { v.old = p.target.value }))}
autoComplete="current-password" autoComplete="current-password"
/> />
<TextField <TextField
@ -102,7 +102,7 @@ function ChangePasswd() {
label="新密码" label="新密码"
type="password" type="password"
required required
onChange={p => setPass(produce(v => { v.pass1 = p.target.value; return v }))} onChange={p => setPass(produce(v => { v.pass1 = p.target.value }))}
autoComplete="new-password" autoComplete="new-password"
/> />
<TextField <TextField
@ -113,7 +113,7 @@ function ChangePasswd() {
required required
error={err != ""} error={err != ""}
helperText={err} helperText={err}
onChange={p => setPass(produce(v => { v.pass2 = p.target.value; return v }))} onChange={p => setPass(produce(v => { v.pass2 = p.target.value }))}
autoComplete="new-password" autoComplete="new-password"
/> />
<Button sx={{ marginTop: "1em" }} onClick={handelClick} variant='contained'></Button> <Button sx={{ marginTop: "1em" }} onClick={handelClick} variant='contained'></Button>

View File

@ -55,6 +55,12 @@ func (y *Yggdrasil) PutTexture() http.HandlerFunc {
} }
t := ctx.Value(tokenKey).(*model.TokenClaims) t := ctx.Value(tokenKey).(*model.TokenClaims)
if uuid != t.Subject {
y.logger.DebugContext(ctx, "uuid 不相同")
w.WriteHeader(401)
return
}
model := r.FormValue("model") model := r.FormValue("model")
skin, err := func() ([]byte, error) { skin, err := func() ([]byte, error) {
@ -99,14 +105,8 @@ func (y *Yggdrasil) PutTexture() http.HandlerFunc {
return return
} }
err = y.yggdrasilService.PutTexture(ctx, t, skin, model, uuid, textureType) err = y.yggdrasilService.PutTexture(ctx, t, skin, model, textureType)
if err != nil { if err != nil {
if errors.Is(err, yggdrasilS.ErrUUIDNotEq) {
y.logger.DebugContext(ctx, err.Error())
w.WriteHeader(401)
return
}
y.handleYgError(ctx, w, err) y.handleYgError(ctx, w, err)
return return
} }
@ -139,7 +139,14 @@ func (y *Yggdrasil) DelTexture() http.HandlerFunc {
return return
} }
t := ctx.Value(tokenKey).(*model.TokenClaims) t := ctx.Value(tokenKey).(*model.TokenClaims)
err := y.yggdrasilService.DelTexture(ctx, uuid, t, textureType)
if uuid != t.Subject {
y.logger.DebugContext(ctx, "uuid 不相同")
w.WriteHeader(401)
return
}
err := y.yggdrasilService.DelTexture(ctx, t, textureType)
if err != nil { if err != nil {
if errors.Is(err, yggdrasilS.ErrUUIDNotEq) { if errors.Is(err, yggdrasilS.ErrUUIDNotEq) {
y.logger.DebugContext(ctx, err.Error()) y.logger.DebugContext(ctx, err.Error())

View File

@ -64,11 +64,11 @@ type Config struct {
} }
type EditUser struct { type EditUser struct {
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"omitempty,email"`
Name string `json:"name" validate:"required,min=3,max=16"` Name string `json:"name" validate:"omitempty,min=3,max=16"`
Password string `json:"password"` Password string `json:"password"`
IsAdmin bool `json:"is_admin"` IsAdmin *bool `json:"is_admin"`
IsDisable bool `json:"is_disable"` IsDisable *bool `json:"is_disable"`
DelTextures bool `json:"del_textures"` DelTextures bool `json:"del_textures"`
} }

View File

@ -82,7 +82,7 @@ func newSkinApi(handel *handle.Handel) http.Handler {
r.Use(handel.NeedAuth) r.Use(handel.NeedAuth)
r.Use(handel.NeedAdmin) r.Use(handel.NeedAdmin)
r.Get("/admin/users", handel.ListUser()) r.Get("/admin/users", handel.ListUser())
r.Post("/admin/user/{uid}", handel.EditUser()) r.Patch("/admin/user/{uid}", handel.EditUser())
}) })
return r return r

View File

@ -10,7 +10,6 @@ import (
"github.com/xmdhs/authlib-skin/db/ent/predicate" "github.com/xmdhs/authlib-skin/db/ent/predicate"
"github.com/xmdhs/authlib-skin/db/ent/user" "github.com/xmdhs/authlib-skin/db/ent/user"
"github.com/xmdhs/authlib-skin/db/ent/userprofile" "github.com/xmdhs/authlib-skin/db/ent/userprofile"
"github.com/xmdhs/authlib-skin/db/ent/usertoken"
"github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil" "github.com/xmdhs/authlib-skin/model/yggdrasil"
utilsService "github.com/xmdhs/authlib-skin/service/utils" utilsService "github.com/xmdhs/authlib-skin/service/utils"
@ -82,19 +81,32 @@ func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) er
uuid := "" uuid := ""
changePasswd := false changePasswd := false
err := utils.WithTx(ctx, w.client, func(tx *ent.Tx) error { err := utils.WithTx(ctx, w.client, func(tx *ent.Tx) error {
up := tx.User.UpdateOneID(uid).SetEmail(u.Email) if u.Email != "" {
if u.Password != "" { c, err := tx.User.Query().Where(user.Email(u.Email)).Count(ctx)
p, s := utils.Argon2ID(u.Password) if err != nil {
up = up.SetPassword(p).SetSalt(s) return err
err := tx.UserToken.Update().Where(usertoken.HasUserWith(user.ID(uid))).AddTokenID(1).Exec(ctx) }
if c != 0 {
return ErrExistUser
}
err = tx.User.UpdateOneID(uid).SetEmail(u.Email).Exec(ctx)
if err != nil { if err != nil {
return err return err
} }
changePasswd = true
} }
err := tx.UserProfile.Update().Where(userprofile.HasUserWith(user.ID(uid))).SetName(u.Name).Exec(ctx)
if err != nil { if u.Name != "" {
return err c, err := tx.UserProfile.Query().Where(userprofile.Name(u.Name)).Count(ctx)
if err != nil {
return err
}
if c != 0 {
return ErrExitsName
}
err = tx.UserProfile.Update().Where(userprofile.HasUserWith(user.ID(uid))).SetName(u.Name).Exec(ctx)
if err != nil {
return err
}
} }
if u.DelTextures { if u.DelTextures {
@ -111,21 +123,25 @@ func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) er
} }
} }
} }
state := 0
if u.IsAdmin {
state = utilsService.SetAdmin(state)
}
if u.IsDisable {
state = utilsService.SetDisable(state)
}
up = up.SetState(state) aUser, err := tx.User.Get(ctx, uid)
err = up.Exec(ctx)
if err != nil { if err != nil {
return err return err
} }
state := aUser.State
if u.IsAdmin != nil {
state = utilsService.SetAdmin(state, *u.IsAdmin)
}
if u.IsDisable != nil {
state = utilsService.SetDisable(state, *u.IsDisable)
}
if state != aUser.State {
err := tx.User.UpdateOneID(uid).SetState(state).Exec(ctx)
if err != nil {
return err
}
}
return nil return nil
}) })
if err != nil { if err != nil {

View File

@ -147,10 +147,16 @@ func IsDisable(state int) bool {
return state&2 == 2 return state&2 == 2
} }
func SetAdmin(state int) int { func SetAdmin(state int, is bool) int {
return state | 1 if is {
return state | 1
}
return state & (state ^ 1)
} }
func SetDisable(state int) int { func SetDisable(state int, is bool) int {
return state | 2 if is {
return state | 2
}
return state & (state ^ 2)
} }

View File

@ -26,10 +26,7 @@ func (y *Yggdrasil) delTexture(ctx context.Context, userProfileID int, textureTy
return utilsService.DelTexture(ctx, userProfileID, textureType, y.client, y.config) return utilsService.DelTexture(ctx, userProfileID, textureType, y.client, y.config)
} }
func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, t *model.TokenClaims, textureType string) error { func (y *Yggdrasil) DelTexture(ctx context.Context, t *model.TokenClaims, textureType string) error {
if uuid != t.Subject {
return fmt.Errorf("PutTexture: %w", ErrUUIDNotEq)
}
up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx) up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx)
if err != nil { if err != nil {
return fmt.Errorf("DelTexture: %w", err) return fmt.Errorf("DelTexture: %w", err)
@ -38,18 +35,14 @@ func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, t *model.TokenC
if err != nil { if err != nil {
return fmt.Errorf("DelTexture: %w", err) return fmt.Errorf("DelTexture: %w", err)
} }
err = y.cache.Del([]byte("Profile" + uuid)) err = y.cache.Del([]byte("Profile" + t.Subject))
if err != nil { if err != nil {
return fmt.Errorf("DelTexture: %w", err) return fmt.Errorf("DelTexture: %w", err)
} }
return nil return nil
} }
func (y *Yggdrasil) PutTexture(ctx context.Context, t *model.TokenClaims, texturebyte []byte, model string, uuid string, textureType string) error { func (y *Yggdrasil) PutTexture(ctx context.Context, t *model.TokenClaims, texturebyte []byte, model string, textureType string) error {
if uuid != t.Subject {
return fmt.Errorf("PutTexture: %w", ErrUUIDNotEq)
}
up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx) up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx)
if err != nil { if err != nil {
return fmt.Errorf("PutTexture: %w", err) return fmt.Errorf("PutTexture: %w", err)
@ -96,7 +89,7 @@ func (y *Yggdrasil) PutTexture(ctx context.Context, t *model.TokenClaims, textur
if err != nil { if err != nil {
return fmt.Errorf("PutTexture: %w", err) return fmt.Errorf("PutTexture: %w", err)
} }
err = y.cache.Del([]byte("Profile" + uuid)) err = y.cache.Del([]byte("Profile" + t.Subject))
if err != nil { if err != nil {
return fmt.Errorf("PutTexture: %w", err) return fmt.Errorf("PutTexture: %w", err)
} }