From cad8e6e317b90c0a9166a7470e797ca9a644032c Mon Sep 17 00:00:00 2001 From: xmdhs Date: Wed, 11 Oct 2023 16:36:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=20yggdrasil=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20=E7=9A=AE=E8=82=A4=E4=B8=8A=E4=BC=A0=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 因为几乎没有几个启动器实现了皮肤上传,所以删除了也几乎没有影响 --- cmd/authlibskin/config.yaml.template | 1 + config/config.yaml | 53 -------------- frontend/src/apis/apis.ts | 16 ++--- frontend/src/apis/model.ts | 7 +- frontend/src/hooks/useTitle.ts | 8 +-- frontend/src/views/Layout.tsx | 8 +-- frontend/src/views/profile/Textures.tsx | 7 +- handle/user.go | 66 +++++++++++++++++ handle/yggdrasil/texture.go | 72 ------------------- model/model.go | 5 +- server/route/route.go | 3 +- service/admin.go | 2 +- service/captcha.go | 5 +- service/texture.go | 96 +++++++++++++++++++++++++ service/utils/texture.go | 5 +- service/yggdrasil/texture.go | 87 +--------------------- 16 files changed, 190 insertions(+), 251 deletions(-) delete mode 100644 config/config.yaml create mode 100644 service/texture.go diff --git a/cmd/authlibskin/config.yaml.template b/cmd/authlibskin/config.yaml.template index 4c446f3..7ef46d2 100644 --- a/cmd/authlibskin/config.yaml.template +++ b/cmd/authlibskin/config.yaml.template @@ -42,6 +42,7 @@ texturePath: "skin" # 材质静态文件提供基础地址 # 如果静态文件位于 oss 上,比如 https://s3.amazonaws.com/example/1.png # 则填写 https://s3.amazonaws.com/example +# 若不需要可不填写 textureBaseUrl: "" # 用于在支持的启动器中展示本站的注册地址 diff --git a/config/config.yaml b/config/config.yaml deleted file mode 100644 index a915560..0000000 --- a/config/config.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# 为 true 则 uuid 生成方式于离线模式相同,若从离线模式切换不会丢失数据。 -# 已有用户数据的情况下勿更改此项 -offlineUUID: true - -port: "127.0.0.1:8080" - -Log: - Level: "debug" - # json 格式输出 - Json: false - -sql: - dsn: "123" - -# 输出每条执行的 sql 语句 -debug: false - -cache: - # 默认使用内存缓存,若需要集群部署,请更换 redis - type: "" - # 内存缓存使用大小,单位 b - ram: 10000000 - -# 位于反向代理后启用,用于记录真实 ip -raelIP: false - -# ip 段最大注册用户,ipv4 为 /24 ipv6 为 /48 -maxIpUser: 10 - -# 运行后勿修改,若为集群需设置为一致 -rsaPriKey: "" - -# 材质文件保存路径,如果需要对象存储可以把对象储存挂载到本地目录上 -texturePath: "skin" - -# 材质静态文件提供基础地址 -# 如果静态文件位于 oss 上,比如 https://s3.amazonaws.com/example/1.png -# 则填写 https://s3.amazonaws.com/example -textureBaseUrl: "" - -# 用于在支持的启动器中展示本站的注册地址 -# 填写类似 https://example.com -webBaseUrl: "" - -# 皮肤站名字,用于在多个地方展示 -serverName: "" - -captcha: - # 验证码类型,目前只支持 cloudflare turnstile - # 填写 turnstile - type: "" - siteKey: "" - secret: "" diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts index e34773b..2cca4fb 100644 --- a/frontend/src/apis/apis.ts +++ b/frontend/src/apis/apis.ts @@ -1,4 +1,4 @@ -import type { tokenData, ApiUser, ApiServerInfo, YggProfile, ApiConfig, List, UserInfo, EditUser } from '@/apis/model' +import type { tokenData, ApiUser, YggProfile, ApiConfig, List, UserInfo, EditUser } from '@/apis/model' import { apiGet } from '@/apis/utils' import root from '@/utils/root' @@ -37,12 +37,6 @@ export async function userInfo(token: string) { return await apiGet(v) } - -export async function serverInfo() { - const v = await fetch(root() + "/api/yggdrasil") - return await v.json() as ApiServerInfo -} - export async function yggProfile(uuid: string) { if (uuid == "") return const v = await fetch(root() + "/api/yggdrasil/sessionserver/session/minecraft/profile/" + uuid) @@ -53,21 +47,19 @@ export async function yggProfile(uuid: string) { return data as YggProfile } -export async function upTextures(uuid: string, token: string, textureType: 'skin' | 'cape', model: 'slim' | '', file: File) { +export async function upTextures(token: string, textureType: 'skin' | 'cape', model: 'slim' | '', file: File) { const f = new FormData() f.set("file", file) f.set("model", model) - const r = await fetch(root() + "/api/yggdrasil/api/user/profile/" + uuid + "/" + textureType, { + const r = await fetch(root() + "/api/v1/user/skin/" + textureType, { method: "PUT", body: f, headers: { "Authorization": "Bearer " + token } }) - if (r.status != 204) { - throw new Error("上传失败 " + String(r.status)) - } + return await apiGet(r) } export async function changePasswd(old: string, newpa: string, token: string) { diff --git a/frontend/src/apis/model.ts b/frontend/src/apis/model.ts index 861a435..cb930d6 100644 --- a/frontend/src/apis/model.ts +++ b/frontend/src/apis/model.ts @@ -28,12 +28,6 @@ export interface ApiUser { is_admin: boolean } -export interface ApiServerInfo { - meta: { - serverName: string - } -} - export interface YggProfile { name: string properties: { @@ -45,6 +39,7 @@ export interface YggProfile { export interface ApiConfig { captcha: captcha AllowChangeName: boolean + serverName: string } export interface UserInfo { diff --git a/frontend/src/hooks/useTitle.ts b/frontend/src/hooks/useTitle.ts index 6513963..c66e3f0 100644 --- a/frontend/src/hooks/useTitle.ts +++ b/frontend/src/hooks/useTitle.ts @@ -1,16 +1,16 @@ -import { serverInfo } from '@/apis/apis' +import { getConfig } from '@/apis/apis' import { useTitle as auseTitle, useRequest } from 'ahooks' import { useEffect } from 'react' export default function useTitle(title: string) { - const { data, error } = useRequest(serverInfo, { - cacheKey: "/api/yggdrasil", + const { data, error } = useRequest(getConfig, { + cacheKey: "/api/v1/config", staleTime: 60000, }) useEffect(() => { error && console.warn(error) }, [error]) - auseTitle(title + " - " + data?.meta.serverName ?? "", { + auseTitle(title + " - " + data?.serverName ?? "", { restoreOnUnmount: true }) } \ No newline at end of file diff --git a/frontend/src/views/Layout.tsx b/frontend/src/views/Layout.tsx index 114c4cd..d903cec 100644 --- a/frontend/src/views/Layout.tsx +++ b/frontend/src/views/Layout.tsx @@ -23,7 +23,7 @@ import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; import Button from '@mui/material/Button'; import { useNavigate } from "react-router-dom"; import { useRequest, useMemoizedFn } from 'ahooks'; -import { serverInfo, userInfo } from '@/apis/apis' +import { getConfig, userInfo } from '@/apis/apis' import Snackbar from '@mui/material/Snackbar'; import Alert from '@mui/material/Alert'; import { memo } from 'react'; @@ -94,8 +94,8 @@ const MyToolbar = memo(function MyToolbar() { const setErr = useSetAtom(LayoutAlertErr) const setOpen = useSetAtom(DrawerOpen) - const server = useRequest(serverInfo, { - cacheKey: "/api/yggdrasil", + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", staleTime: 60000, onError: e => { console.warn(e) @@ -131,7 +131,7 @@ const MyToolbar = memo(function MyToolbar() { } - {server.data?.meta.serverName ?? "皮肤站"} + {server.data?.serverName ?? "皮肤站"} {nowUser.name != "" && ( diff --git a/frontend/src/views/profile/Textures.tsx b/frontend/src/views/profile/Textures.tsx index 93db294..8240834 100644 --- a/frontend/src/views/profile/Textures.tsx +++ b/frontend/src/views/profile/Textures.tsx @@ -14,7 +14,7 @@ import Box from "@mui/material/Box"; import ReactSkinview3d from '@/components/Skinview3d' import { useUnmount } from "ahooks"; import { useAtomValue, useSetAtom } from "jotai"; -import { LayoutAlertErr, token, user } from "@/store/store"; +import { LayoutAlertErr, token } from "@/store/store"; import { upTextures } from "@/apis/apis"; import Loading from "@/components/Loading"; import Snackbar from "@mui/material/Snackbar"; @@ -25,7 +25,6 @@ const Textures = function Textures() { const [file, setFile] = useState(null) const setErr = useSetAtom(LayoutAlertErr) const [loading, setLoading] = useState(false) - const userinfo = useAtomValue(user) const nowToken = useAtomValue(token) const [ok, setOk] = useState(false) const [skinInfo, setSkinInfo] = useState({ @@ -73,8 +72,8 @@ const Textures = function Textures() { setLoading(true) const textureType = redioValue == "cape" ? "cape" : "skin" const model = redioValue == "slim" ? "slim" : "" - upTextures(userinfo.uuid, nowToken, textureType, model, file).catch(e => [setErr(String(e)), console.warn(e)]). - finally(() => setLoading(false)).then(() => setOk(true)) + upTextures(nowToken, textureType, model, file).then(() => setOk(true)).catch(e => [setErr(String(e)), console.warn(e)]). + finally(() => setLoading(false)) } diff --git a/handle/user.go b/handle/user.go index 1d60929..ecc0faa 100644 --- a/handle/user.go +++ b/handle/user.go @@ -1,9 +1,14 @@ package handle import ( + "bytes" + "fmt" + "image/png" + "io" "log/slog" "net/http" + "github.com/go-chi/chi/v5" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/utils" ) @@ -123,3 +128,64 @@ func (h *Handel) ChangeName() http.HandlerFunc { }) } } + +func (h *Handel) PutTexture() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + t := ctx.Value(tokenKey).(*model.TokenClaims) + models := r.FormValue("model") + + textureType := chi.URLParamFromCtx(ctx, "textureType") + if textureType != "skin" && textureType != "cape" { + h.logger.DebugContext(ctx, "上传类型错误") + h.handleError(ctx, w, "上传类型错误", model.ErrInput, 400, slog.LevelDebug) + } + + skin, err := func() ([]byte, error) { + f, _, err := r.FormFile("file") + if err != nil { + return nil, err + } + b, err := io.ReadAll(io.LimitReader(f, 50*1000)) + if err != nil { + return nil, err + } + pc, err := png.DecodeConfig(bytes.NewReader(b)) + if err != nil { + return nil, err + } + if pc.Height > 200 || pc.Width > 200 { + return nil, fmt.Errorf("材质大小超过限制") + } + p, err := png.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + bw := bytes.NewBuffer(nil) + err = png.Encode(bw, p) + return bw.Bytes(), err + }() + if err != nil { + h.handleError(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + + switch models { + case "slim": + case "": + default: + h.logger.DebugContext(ctx, "错误的皮肤的材质模型") + h.handleError(ctx, w, "错误的皮肤的材质模型", model.ErrInput, 400, slog.LevelDebug) + return + } + + err = h.webService.PutTexture(ctx, t, skin, models, textureType) + if err != nil { + h.handleErrorService(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + } +} diff --git a/handle/yggdrasil/texture.go b/handle/yggdrasil/texture.go index 39a1abb..2ca4069 100644 --- a/handle/yggdrasil/texture.go +++ b/handle/yggdrasil/texture.go @@ -1,12 +1,8 @@ package yggdrasil import ( - "bytes" "context" "errors" - "fmt" - "image/png" - "io" "log/slog" "net/http" "strings" @@ -46,74 +42,6 @@ func (y *Yggdrasil) validTextureType(ctx context.Context, w http.ResponseWriter, return true } -func (y *Yggdrasil) PutTexture() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - uuid, textureType, ok := getUUIDbyParams(ctx, y.logger, w) - if !ok { - return - } - t := ctx.Value(tokenKey).(*model.TokenClaims) - - if uuid != t.Subject { - y.logger.DebugContext(ctx, "uuid 不相同") - w.WriteHeader(401) - return - } - - model := r.FormValue("model") - - skin, err := func() ([]byte, error) { - f, _, err := r.FormFile("file") - if err != nil { - return nil, err - } - b, err := io.ReadAll(io.LimitReader(f, 50*1000)) - if err != nil { - return nil, err - } - pc, err := png.DecodeConfig(bytes.NewReader(b)) - if err != nil { - return nil, err - } - if pc.Height > 200 || pc.Width > 200 { - return nil, fmt.Errorf("材质大小超过限制") - } - p, err := png.Decode(bytes.NewReader(b)) - if err != nil { - return nil, err - } - bw := bytes.NewBuffer(nil) - err = png.Encode(bw, p) - return bw.Bytes(), err - }() - if err != nil { - y.logger.DebugContext(ctx, err.Error()) - handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: err.Error()}, 400) - return - } - if !y.validTextureType(ctx, w, textureType) { - return - } - - switch model { - case "slim": - case "": - default: - y.logger.DebugContext(ctx, "错误的皮肤的材质模型") - handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "错误的皮肤的材质模型"}, 400) - return - } - - err = y.yggdrasilService.PutTexture(ctx, t, skin, model, textureType) - if err != nil { - y.handleYgError(ctx, w, err) - return - } - w.WriteHeader(204) - } -} - func getUUIDbyParams(ctx context.Context, l *slog.Logger, w http.ResponseWriter) (string, string, bool) { uuid := chi.URLParamFromCtx(ctx, "uuid") textureType := chi.URLParamFromCtx(ctx, "textureType") diff --git a/model/model.go b/model/model.go index b31b33b..ec6f5b3 100644 --- a/model/model.go +++ b/model/model.go @@ -31,8 +31,9 @@ type TokenClaims struct { } type Captcha struct { - Type string `json:"type"` - SiteKey string `json:"siteKey"` + Type string `json:"type"` + SiteKey string `json:"siteKey"` + ServerName string `json:"serverName"` } type UserInfo struct { diff --git a/server/route/route.go b/server/route/route.go index cd0c164..1e6f64c 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -46,7 +46,6 @@ func newYggdrasil(handelY *yggdrasil.Yggdrasil) http.Handler { r.Post("/authserver/invalidate", handelY.Invalidate()) r.Post("/authserver/refresh", handelY.Refresh()) - r.Put("/api/user/profile/{uuid}/{textureType}", handelY.PutTexture()) r.Delete("/api/user/profile/{uuid}/{textureType}", handelY.DelTexture()) r.Post("/sessionserver/session/minecraft/join", handelY.SessionJoin()) @@ -78,7 +77,7 @@ func newSkinApi(handel *handle.Handel) http.Handler { r.Get("/user", handel.UserInfo()) r.Post("/user/password", handel.ChangePasswd()) r.Post("/user/name", handel.ChangeName()) - + r.Put("/user/skin/{textureType}", handel.PutTexture()) }) r.Group(func(r chi.Router) { diff --git a/service/admin.go b/service/admin.go index a2031bf..7cd4a72 100644 --- a/service/admin.go +++ b/service/admin.go @@ -117,7 +117,7 @@ func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) er uuid = userProfile.UUID tl := []string{"skin", "cape"} for _, v := range tl { - err := utilsService.DelTexture(ctx, userProfile.ID, v, w.client, w.config) + err := utilsService.DelTexture(ctx, userProfile.ID, v, w.client, w.config.TexturePath) if err != nil { return err } diff --git a/service/captcha.go b/service/captcha.go index 77155d3..f0700ec 100644 --- a/service/captcha.go +++ b/service/captcha.go @@ -15,8 +15,9 @@ import ( func (w *WebService) GetConfig(ctx context.Context) model.Config { return model.Config{ Captcha: model.Captcha{ - Type: w.config.Captcha.Type, - SiteKey: w.config.Captcha.SiteKey, + Type: w.config.Captcha.Type, + SiteKey: w.config.Captcha.SiteKey, + ServerName: w.config.ServerName, }, AllowChangeName: !w.config.OfflineUUID, } diff --git a/service/texture.go b/service/texture.go new file mode 100644 index 0000000..b4784e6 --- /dev/null +++ b/service/texture.go @@ -0,0 +1,96 @@ +package service + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/texture" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/model" + utilsService "github.com/xmdhs/authlib-skin/service/utils" + "github.com/xmdhs/authlib-skin/utils" +) + +func (w *WebService) PutTexture(ctx context.Context, t *model.TokenClaims, texturebyte []byte, model string, textureType string) error { + up, err := w.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + err = utilsService.DelTexture(ctx, up.ID, textureType, w.client, w.config.TexturePath) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + + hashstr := getHash(texturebyte) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + u, err := w.client.User.Query().Where(user.HasProfileWith(userprofile.ID(up.ID))).Only(ctx) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + + err = utils.WithTx(ctx, w.client, func(tx *ent.Tx) error { + t, err := tx.Texture.Query().Where(texture.TextureHash(hashstr)).Only(ctx) + if err != nil { + var ne *ent.NotFoundError + if !errors.As(err, &ne) { + return err + } + } + if t == nil { + t, err = tx.Texture.Create().SetCreatedUser(u).SetTextureHash(hashstr).Save(ctx) + if err != nil { + return err + } + } + err = tx.UserTexture.Create().SetTexture(t).SetType(textureType).SetUserProfile(up).SetVariant(model).Exec(ctx) + if err != nil { + return err + } + return nil + }) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + err = createTextureFile(w.config.TexturePath, texturebyte, hashstr) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + err = w.cache.Del([]byte("Profile" + t.Subject)) + if err != nil { + return fmt.Errorf("PutTexture: %w", err) + } + return nil +} + +func getHash(b []byte) string { + hashed := sha256.Sum256(b) + return hex.EncodeToString(hashed[:]) +} + +func createTextureFile(path string, b []byte, hashstr string) error { + p := filepath.Join(path, hashstr[:2], hashstr[2:4], hashstr) + err := os.MkdirAll(filepath.Dir(p), 0755) + if err != nil { + return fmt.Errorf("createTextureFile: %w", err) + } + f, err := os.Stat(p) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("createTextureFile: %w", err) + } + if f == nil { + err := os.WriteFile(p, b, 0644) + if err != nil { + return fmt.Errorf("createTextureFile: %w", err) + } + } + return nil +} diff --git a/service/utils/texture.go b/service/utils/texture.go index 8b2dda0..6b63af2 100644 --- a/service/utils/texture.go +++ b/service/utils/texture.go @@ -8,13 +8,12 @@ import ( "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 { +func DelTexture(ctx context.Context, userProfileID int, textureType string, client *ent.Client, texturePath string) error { // 查找此用户该类型下是否已经存在皮肤 tl, err := client.UserTexture.Query().Where(usertexture.And( usertexture.UserProfileID(userProfileID), @@ -42,7 +41,7 @@ func DelTexture(ctx context.Context, userProfileID int, textureType string, clie } return fmt.Errorf("DelTexture: %w", err) } - path := filepath.Join(config.TexturePath, t.TextureHash[:2], t.TextureHash[2:4], t.TextureHash) + path := filepath.Join(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) diff --git a/service/yggdrasil/texture.go b/service/yggdrasil/texture.go index 190de74..503a61a 100644 --- a/service/yggdrasil/texture.go +++ b/service/yggdrasil/texture.go @@ -2,20 +2,13 @@ package yggdrasil import ( "context" - "crypto/sha256" - "encoding/hex" "errors" "fmt" - "os" - "path/filepath" - "github.com/xmdhs/authlib-skin/db/ent" - "github.com/xmdhs/authlib-skin/db/ent/texture" "github.com/xmdhs/authlib-skin/db/ent/user" "github.com/xmdhs/authlib-skin/db/ent/userprofile" "github.com/xmdhs/authlib-skin/model" utilsService "github.com/xmdhs/authlib-skin/service/utils" - "github.com/xmdhs/authlib-skin/utils" ) var ( @@ -23,7 +16,7 @@ var ( ) func (y *Yggdrasil) delTexture(ctx context.Context, userProfileID int, textureType string) error { - return utilsService.DelTexture(ctx, userProfileID, textureType, y.client, y.config) + return utilsService.DelTexture(ctx, userProfileID, textureType, y.client, y.config.TexturePath) } func (y *Yggdrasil) DelTexture(ctx context.Context, t *model.TokenClaims, textureType string) error { @@ -41,81 +34,3 @@ func (y *Yggdrasil) DelTexture(ctx context.Context, t *model.TokenClaims, textur } return nil } - -func (y *Yggdrasil) PutTexture(ctx context.Context, t *model.TokenClaims, texturebyte []byte, model string, textureType string) error { - up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - - err = y.delTexture(ctx, up.ID, textureType) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - - hashstr := getHash(texturebyte) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - u, err := y.client.User.Query().Where(user.HasProfileWith(userprofile.ID(up.ID))).Only(ctx) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - - err = utils.WithTx(ctx, y.client, func(tx *ent.Tx) error { - t, err := tx.Texture.Query().Where(texture.TextureHash(hashstr)).Only(ctx) - if err != nil { - var ne *ent.NotFoundError - if !errors.As(err, &ne) { - return err - } - } - if t == nil { - t, err = tx.Texture.Create().SetCreatedUser(u).SetTextureHash(hashstr).Save(ctx) - if err != nil { - return err - } - } - err = tx.UserTexture.Create().SetTexture(t).SetType(textureType).SetUserProfile(up).SetVariant(model).Exec(ctx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - err = createTextureFile(y.config.TexturePath, texturebyte, hashstr) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - err = y.cache.Del([]byte("Profile" + t.Subject)) - if err != nil { - return fmt.Errorf("PutTexture: %w", err) - } - return nil -} - -func getHash(b []byte) string { - hashed := sha256.Sum256(b) - return hex.EncodeToString(hashed[:]) -} - -func createTextureFile(path string, b []byte, hashstr string) error { - p := filepath.Join(path, hashstr[:2], hashstr[2:4], hashstr) - err := os.MkdirAll(filepath.Dir(p), 0755) - if err != nil { - return fmt.Errorf("createTextureFile: %w", err) - } - f, err := os.Stat(p) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("createTextureFile: %w", err) - } - if f == nil { - err := os.WriteFile(p, b, 0644) - if err != nil { - return fmt.Errorf("createTextureFile: %w", err) - } - } - return nil -}