修改用户

This commit is contained in:
xmdhs 2023-10-08 18:28:35 +08:00
parent b2eb6ee28e
commit 4cf7c8aca0
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
10 changed files with 205 additions and 58 deletions

View File

@ -1,4 +1,4 @@
import type { tokenData, ApiUser, ApiServerInfo, YggProfile, ApiConfig, List, UserInfo } from '@/apis/model' import type { tokenData, ApiUser, ApiServerInfo, YggProfile, ApiConfig, List, UserInfo, EditUser } from '@/apis/model'
import { apiGet } from '@/apis/utils' import { apiGet } from '@/apis/utils'
export async function login(username: string, password: string) { export async function login(username: string, password: string) {
@ -117,3 +117,14 @@ export async function ListUser(page: number, token: string, email: string, name:
}) })
return await apiGet<List<UserInfo>>(r) return await apiGet<List<UserInfo>>(r)
} }
export async function editUser(u: EditUser, token: string, uid: string) {
const r = await fetch(import.meta.env.VITE_APIADDR + "/api/v1/admin/user/" + uid,{
method: "POST",
headers: {
"Authorization": "Bearer " + token
},
body: JSON.stringify(u)
})
return await apiGet<unknown>(r)
}

View File

@ -57,3 +57,10 @@ export interface UserInfo {
reg_ip: string reg_ip: string
name: string name: string
} }
export interface EditUser {
email: string
name: string
password: string
is_admin: boolean
}

View File

@ -6,7 +6,7 @@ import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow'; import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import useTitle from '@/hooks/useTitle'; import useTitle from '@/hooks/useTitle';
import { useMemoizedFn, useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { ListUser } from '@/apis/apis'; import { ListUser } from '@/apis/apis';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
@ -113,15 +113,19 @@ interface MyDialogProp {
} }
function MyDialog({ open, row, setOpen }: MyDialogProp) { function MyDialog({ open, row, setOpen }: MyDialogProp) {
const handleClose = useMemoizedFn(() => { const handleClose = () => {
setOpen(false) setOpen(false)
}) }
const [nrow, setNrow] = useState(row) const [nrow, setNrow] = useState(row)
useEffect(() => { useEffect(() => {
setNrow(row) setNrow(row)
}, [row]) }, [row])
const handleOpen = () => {
}
return ( return (
<Dialog open={open}> <Dialog open={open}>
@ -143,7 +147,7 @@ function MyDialog({ open, row, setOpen }: MyDialogProp) {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleClose}></Button> <Button onClick={handleClose}></Button>
<Button onClick={handleClose}></Button> <Button onClick={handleOpen}></Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
) )

View File

@ -7,9 +7,12 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/go-chi/chi/v5"
"github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/service" "github.com/xmdhs/authlib-skin/service"
"github.com/xmdhs/authlib-skin/service/utils" "github.com/xmdhs/authlib-skin/service/utils"
U "github.com/xmdhs/authlib-skin/utils"
) )
type tokenValue string type tokenValue string
@ -81,3 +84,33 @@ func (h *Handel) ListUser() http.HandlerFunc {
encodeJson(w, model.API[model.List[model.UserList]]{Data: model.List[model.UserList]{List: ul, Total: uc}}) encodeJson(w, model.API[model.List[model.UserList]]{Data: model.List[model.UserList]{List: ul, Total: uc}})
} }
} }
func (h *Handel) EditUser() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
uid := chi.URLParamFromCtx(ctx, "uid")
if uid == "" {
h.handleError(ctx, w, "uid 为空", model.ErrInput, 400, slog.LevelDebug)
return
}
uidi, err := strconv.Atoi(uid)
if err != nil {
h.handleError(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return
}
a, err := U.DeCodeBody[model.EditUser](r.Body, h.validate)
if err != nil {
h.handleError(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return
}
err = h.webService.EditUser(ctx, a, uidi)
if err != nil {
h.handleError(ctx, w, err.Error(), model.ErrService, 500, slog.LevelWarn)
return
}
encodeJson[any](w, model.API[any]{
Code: 0,
})
}
}

View File

@ -61,3 +61,12 @@ type Config struct {
Captcha Captcha `json:"captcha"` Captcha Captcha `json:"captcha"`
AllowChangeName bool AllowChangeName bool
} }
type EditUser struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required,min=3,max=16"`
Password string `json:"password"`
IsAdmin bool `json:"is_admin"`
IsDisable bool `json:"is_disable"`
DelTextures bool `json:"del_textures"`
}

View File

@ -81,6 +81,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())
}) })
return r return r

View File

@ -5,12 +5,15 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/xmdhs/authlib-skin/db/ent"
"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"
"github.com/xmdhs/authlib-skin/utils"
) )
var ErrNotAdmin = errors.New("无权限") var ErrNotAdmin = errors.New("无权限")
@ -72,3 +75,56 @@ func (w *WebService) ListUser(ctx context.Context, page int, email, name string)
} }
return ul, uc, nil return ul, uc, nil
} }
func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) error {
err := utils.WithTx(ctx, w.client, func(tx *ent.Tx) error {
up := tx.User.UpdateOneID(uid).SetEmail(u.Email)
if u.Password != "" {
p, s := utils.Argon2ID(u.Password)
up = up.SetPassword(p).SetSalt(s)
err := tx.UserToken.Update().Where(usertoken.HasUserWith(user.ID(uid))).AddTokenID(1).Exec(ctx)
if err != nil {
return err
}
}
err := tx.UserProfile.Update().Where(userprofile.HasUserWith(user.ID(uid))).SetName(u.Name).Exec(ctx)
if err != nil {
return err
}
if u.DelTextures {
up, err := tx.UserProfile.Query().Where(userprofile.ID(uid)).First(ctx)
if err != nil {
return err
}
tl := []string{"skin", "cape"}
for _, v := range tl {
err := utilsService.DelTexture(ctx, up.ID, v, w.client, w.config)
if err != nil {
return err
}
}
}
state := 0
if u.IsAdmin {
state = utilsService.SetAdmin(state)
}
if u.IsDisable {
state = utilsService.SetDisable(state)
}
up = up.SetState(state)
err = up.Exec(ctx)
if err != nil {
return err
}
return nil
})
if err != nil {
return fmt.Errorf("EditUser: %w", err)
}
return nil
}

View File

@ -87,3 +87,11 @@ func IsAdmin(state int) bool {
func IsDisable(state int) bool { func IsDisable(state int) bool {
return state&2 == 2 return state&2 == 2
} }
func SetAdmin(state int) int {
return state | 1
}
func SetDisable(state int) int {
return state | 2
}

69
service/utils/texture.go Normal file
View File

@ -0,0 +1,69 @@
package utils
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/samber/lo"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/db/ent"
"github.com/xmdhs/authlib-skin/db/ent/texture"
"github.com/xmdhs/authlib-skin/db/ent/usertexture"
)
func DelTexture(ctx context.Context, userProfileID int, textureType string, client *ent.Client, config config.Config) error {
// 查找此用户该类型下是否已经存在皮肤
tl, err := client.UserTexture.Query().Where(usertexture.And(
usertexture.UserProfileID(userProfileID),
usertexture.Type(textureType),
)).All(ctx)
if err != nil {
return fmt.Errorf("DelTexture: %w", err)
}
if len(tl) == 0 {
return nil
}
// 若存在,查找是否被引用
for _, v := range tl {
c, err := client.UserTexture.Query().Where(usertexture.TextureID(v.TextureID)).Count(ctx)
if err != nil {
return fmt.Errorf("DelTexture: %w", err)
}
if c == 1 {
// 若没有其他用户使用该皮肤,删除文件和记录
t, err := client.Texture.Query().Where(texture.ID(v.TextureID)).Only(ctx)
if err != nil {
var nf *ent.NotFoundError
if errors.As(err, &nf) {
continue
}
return fmt.Errorf("DelTexture: %w", err)
}
path := filepath.Join(config.TexturePath, t.TextureHash[:2], t.TextureHash[2:4], t.TextureHash)
err = os.Remove(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("DelTexture: %w", err)
}
// Texture 表中删除记录
err = client.Texture.DeleteOneID(v.TextureID).Exec(ctx)
if err != nil {
return fmt.Errorf("DelTexture: %w", err)
}
}
}
ids := lo.Map[*ent.UserTexture, int](tl, func(item *ent.UserTexture, index int) int {
return item.ID
})
// 中间表删除记录
// UserProfile 上没有于此相关的字段,所以无需操作
_, err = client.UserTexture.Delete().Where(usertexture.IDIn(ids...)).Exec(ctx)
// 小概率皮肤上传后,高并发时被此处清理。问题不大重新上传一遍就行。
// 条件为使用一个独一无二的皮肤的用户,更换皮肤时,另一个用户同时更换自己的皮肤到这个独一无二的皮肤上。
if err != nil {
return fmt.Errorf("DelTexture: %w", err)
}
return nil
}

View File

@ -9,13 +9,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/samber/lo"
"github.com/xmdhs/authlib-skin/db/ent" "github.com/xmdhs/authlib-skin/db/ent"
"github.com/xmdhs/authlib-skin/db/ent/texture" "github.com/xmdhs/authlib-skin/db/ent/texture"
"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/usertexture"
"github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/model"
utilsService "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/utils" "github.com/xmdhs/authlib-skin/utils"
) )
@ -24,57 +23,7 @@ var (
) )
func (y *Yggdrasil) delTexture(ctx context.Context, userProfileID int, textureType string) error { func (y *Yggdrasil) delTexture(ctx context.Context, userProfileID int, textureType string) error {
// 查找此用户该类型下是否已经存在皮肤 return utilsService.DelTexture(ctx, userProfileID, textureType, y.client, y.config)
tl, err := y.client.UserTexture.Query().Where(usertexture.And(
usertexture.UserProfileID(userProfileID),
usertexture.Type(textureType),
)).All(ctx)
if err != nil {
return fmt.Errorf("delTexture: %w", err)
}
if len(tl) == 0 {
return nil
}
// 若存在,查找是否被引用
for _, v := range tl {
c, err := y.client.UserTexture.Query().Where(usertexture.TextureID(v.TextureID)).Count(ctx)
if err != nil {
return fmt.Errorf("delTexture: %w", err)
}
if c == 1 {
// 若没有其他用户使用该皮肤,删除文件和记录
t, err := y.client.Texture.Query().Where(texture.ID(v.TextureID)).Only(ctx)
if err != nil {
var nf *ent.NotFoundError
if errors.As(err, &nf) {
continue
}
return fmt.Errorf("delTexture: %w", err)
}
path := filepath.Join(y.config.TexturePath, t.TextureHash[:2], t.TextureHash[2:4], t.TextureHash)
err = os.Remove(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("delTexture: %w", err)
}
// Texture 表中删除记录
err = y.client.Texture.DeleteOneID(v.TextureID).Exec(ctx)
if err != nil {
return fmt.Errorf("delTexture: %w", err)
}
}
}
ids := lo.Map[*ent.UserTexture, int](tl, func(item *ent.UserTexture, index int) int {
return item.ID
})
// 中间表删除记录
// UserProfile 上没有于此相关的字段,所以无需操作
_, err = y.client.UserTexture.Delete().Where(usertexture.IDIn(ids...)).Exec(ctx)
// 小概率皮肤上传后,高并发时被此处清理。问题不大重新上传一遍就行。
// 条件为使用一个独一无二的皮肤的用户,更换皮肤时,另一个用户同时更换自己的皮肤到这个独一无二的皮肤上。
if err != nil {
return fmt.Errorf("delTexture: %w", err)
}
return nil
} }
func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, t *model.TokenClaims, textureType string) error { func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, t *model.TokenClaims, textureType string) error {