From 145bb045e97cf93cfb6828e31675ebd9bb760d90 Mon Sep 17 00:00:00 2001 From: thehrz Date: Fri, 24 Jan 2025 16:57:58 +0800 Subject: [PATCH] fork from xmdhs/authlib-skin --- .gitea/workflows/ci.yml | 25 + .gitignore | 8 + .vscode/settings.json | 5 + Dockerfile | 26 + LICENSE | 21 + README.md | 16 + config/config.go | 100 + db/cache/cache.go | 40 + db/cache/cache_test.go | 38 + db/cache/fastcache.go | 55 + db/cache/no_redis.go | 7 + db/cache/redis.go | 53 + db/ent/client.go | 1021 ++++++ db/ent/ent.go | 616 ++++ db/ent/enttest/enttest.go | 84 + db/ent/for_update.tmpl | 40 + db/ent/generate.go | 3 + db/ent/hook/hook.go | 247 ++ db/ent/migrate/migrate.go | 64 + db/ent/migrate/schema.go | 192 + db/ent/mutation.go | 3105 ++++++++++++++++ db/ent/predicate/predicate.go | 22 + db/ent/runtime.go | 9 + db/ent/runtime/runtime.go | 9 + db/ent/schema/texture.go | 37 + db/ent/schema/user.go | 52 + db/ent/schema/userprofile.go | 42 + db/ent/schema/usertexture.go | 51 + db/ent/schema/usertoken.go | 34 + db/ent/texture.go | 176 + db/ent/texture/texture.go | 146 + db/ent/texture/where.go | 225 ++ db/ent/texture_create.go | 275 ++ db/ent/texture_delete.go | 88 + db/ent/texture_query.go | 850 +++++ db/ent/texture_update.go | 634 ++++ db/ent/tx.go | 222 ++ db/ent/user.go | 226 ++ db/ent/user/user.go | 164 + db/ent/user/where.go | 525 +++ db/ent/user_create.go | 348 ++ db/ent/user_delete.go | 88 + db/ent/user_query.go | 805 +++++ db/ent/user_update.go | 704 ++++ db/ent/userprofile.go | 187 + db/ent/userprofile/userprofile.go | 154 + db/ent/userprofile/where.go | 295 ++ db/ent/userprofile_create.go | 288 ++ db/ent/userprofile_delete.go | 88 + db/ent/userprofile_query.go | 850 +++++ db/ent/userprofile_update.go | 652 ++++ db/ent/usertexture.go | 188 + db/ent/usertexture/usertexture.go | 118 + db/ent/usertexture/where.go | 322 ++ db/ent/usertexture_create.go | 262 ++ db/ent/usertexture_delete.go | 88 + db/ent/usertexture_query.go | 737 ++++ db/ent/usertexture_update.go | 425 +++ db/ent/usertoken.go | 142 + db/ent/usertoken/usertoken.go | 82 + db/ent/usertoken/where.go | 154 + db/ent/usertoken_create.go | 216 ++ db/ent/usertoken_delete.go | 88 + db/ent/usertoken_query.go | 670 ++++ db/ent/usertoken_update.go | 322 ++ docker-compose.yml | 23 + frontend/.env | 1 + frontend/.env.development | 1 + frontend/.eslintrc.cjs | 18 + frontend/.gitignore | 24 + frontend/README.md | 27 + frontend/index.html | 13 + frontend/package.json | 39 + frontend/pnpm-lock.yaml | 3195 +++++++++++++++++ frontend/public/vite.svg | 1 + frontend/src/App.tsx | 13 + frontend/src/Route.tsx | 58 + frontend/src/apis/apis.ts | 156 + frontend/src/apis/error.ts | 8 + frontend/src/apis/model.ts | 66 + frontend/src/apis/utils.ts | 10 + frontend/src/components/CaptchaWidget.tsx | 65 + frontend/src/components/CheckInput.tsx | 65 + frontend/src/components/Loading.tsx | 15 + frontend/src/components/NeedLogin.tsx | 41 + frontend/src/components/SkinViewUUID.tsx | 89 + frontend/src/components/Skinview3d.tsx | 101 + frontend/src/hooks/useTitle.ts | 16 + frontend/src/main.tsx | 11 + frontend/src/store/store.ts | 10 + frontend/src/utils/email.ts | 3 + frontend/src/utils/root.ts | 6 + frontend/src/utils/skin.ts | 17 + frontend/src/views/Forgot.tsx | 120 + frontend/src/views/Index.tsx | 9 + frontend/src/views/Layout.tsx | 291 ++ frontend/src/views/Login.tsx | 169 + frontend/src/views/Register.tsx | 225 ++ frontend/src/views/SendEmail.tsx | 181 + frontend/src/views/admin/UserAdmin.tsx | 237 ++ frontend/src/views/profile/Profile.tsx | 70 + frontend/src/views/profile/Security.tsx | 202 ++ frontend/src/views/profile/Textures.tsx | 133 + frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.json | 39 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 17 + go.mod | 49 + go.sum | 136 + handle/admin.go | 137 + handle/config.go | 31 + handle/handelerror/error.go | 67 + handle/handle.go | 29 + handle/user.go | 338 ++ handle/yggdrasil/error.go | 23 + handle/yggdrasil/session.go | 54 + handle/yggdrasil/texture.go | 77 + handle/yggdrasil/user.go | 172 + handle/yggdrasil/yggdrasil.go | 146 + main.go | 51 + model/const.go | 19 + model/model.go | 102 + model/yggdrasil/model.go | 103 + model/yggdrasil/textures.go | 28 + server/provide.go | 114 + server/route/middleware.go | 82 + server/route/route.go | 109 + server/server.go | 19 + server/slog.go | 27 + server/sqlite_init.go | 5 + server/static/static.go | 36 + server/wire.go | 32 + server/wire_gen.go | 90 + service/admin.go | 201 ++ service/admin_test.go | 65 + service/auth/auth.go | 141 + service/auth/utils.go | 52 + service/captcha/captcha.go | 85 + service/email/email.go | 191 + service/service.go | 5 + service/texture.go | 112 + service/user.go | 288 ++ service/user_test.go | 164 + service/utils/texture.go | 68 + service/web.go | 43 + service/yggdrasil/session.go | 57 + service/yggdrasil/texture.go | 36 + service/yggdrasil/user.go | 318 ++ service/yggdrasil/user_test.go | 62 + service/yggdrasil/yggdrasil.go | 111 + .../yggdrasil/yggdrasil_session_pubkey.der | Bin 0 -> 550 bytes test/auth_test.go | 35 + test/config.toml | 107 + test/server_test.go | 26 + utils/argon2id.go | 32 + utils/decode.go | 25 + utils/ip.go | 19 + utils/sign/rsa.go | 90 + utils/sign/rsa_test.go | 105 + utils/tx.go | 31 + utils/uuid.go | 16 + utils/uuid_test.go | 11 + 162 files changed, 28194 insertions(+) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/config.go create mode 100644 db/cache/cache.go create mode 100644 db/cache/cache_test.go create mode 100644 db/cache/fastcache.go create mode 100644 db/cache/no_redis.go create mode 100644 db/cache/redis.go create mode 100644 db/ent/client.go create mode 100644 db/ent/ent.go create mode 100644 db/ent/enttest/enttest.go create mode 100644 db/ent/for_update.tmpl create mode 100644 db/ent/generate.go create mode 100644 db/ent/hook/hook.go create mode 100644 db/ent/migrate/migrate.go create mode 100644 db/ent/migrate/schema.go create mode 100644 db/ent/mutation.go create mode 100644 db/ent/predicate/predicate.go create mode 100644 db/ent/runtime.go create mode 100644 db/ent/runtime/runtime.go create mode 100644 db/ent/schema/texture.go create mode 100644 db/ent/schema/user.go create mode 100644 db/ent/schema/userprofile.go create mode 100644 db/ent/schema/usertexture.go create mode 100644 db/ent/schema/usertoken.go create mode 100644 db/ent/texture.go create mode 100644 db/ent/texture/texture.go create mode 100644 db/ent/texture/where.go create mode 100644 db/ent/texture_create.go create mode 100644 db/ent/texture_delete.go create mode 100644 db/ent/texture_query.go create mode 100644 db/ent/texture_update.go create mode 100644 db/ent/tx.go create mode 100644 db/ent/user.go create mode 100644 db/ent/user/user.go create mode 100644 db/ent/user/where.go create mode 100644 db/ent/user_create.go create mode 100644 db/ent/user_delete.go create mode 100644 db/ent/user_query.go create mode 100644 db/ent/user_update.go create mode 100644 db/ent/userprofile.go create mode 100644 db/ent/userprofile/userprofile.go create mode 100644 db/ent/userprofile/where.go create mode 100644 db/ent/userprofile_create.go create mode 100644 db/ent/userprofile_delete.go create mode 100644 db/ent/userprofile_query.go create mode 100644 db/ent/userprofile_update.go create mode 100644 db/ent/usertexture.go create mode 100644 db/ent/usertexture/usertexture.go create mode 100644 db/ent/usertexture/where.go create mode 100644 db/ent/usertexture_create.go create mode 100644 db/ent/usertexture_delete.go create mode 100644 db/ent/usertexture_query.go create mode 100644 db/ent/usertexture_update.go create mode 100644 db/ent/usertoken.go create mode 100644 db/ent/usertoken/usertoken.go create mode 100644 db/ent/usertoken/where.go create mode 100644 db/ent/usertoken_create.go create mode 100644 db/ent/usertoken_delete.go create mode 100644 db/ent/usertoken_query.go create mode 100644 db/ent/usertoken_update.go create mode 100644 docker-compose.yml create mode 100644 frontend/.env create mode 100644 frontend/.env.development create mode 100644 frontend/.eslintrc.cjs create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/Route.tsx create mode 100644 frontend/src/apis/apis.ts create mode 100644 frontend/src/apis/error.ts create mode 100644 frontend/src/apis/model.ts create mode 100644 frontend/src/apis/utils.ts create mode 100644 frontend/src/components/CaptchaWidget.tsx create mode 100644 frontend/src/components/CheckInput.tsx create mode 100644 frontend/src/components/Loading.tsx create mode 100644 frontend/src/components/NeedLogin.tsx create mode 100644 frontend/src/components/SkinViewUUID.tsx create mode 100644 frontend/src/components/Skinview3d.tsx create mode 100644 frontend/src/hooks/useTitle.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/store/store.ts create mode 100644 frontend/src/utils/email.ts create mode 100644 frontend/src/utils/root.ts create mode 100644 frontend/src/utils/skin.ts create mode 100644 frontend/src/views/Forgot.tsx create mode 100644 frontend/src/views/Index.tsx create mode 100644 frontend/src/views/Layout.tsx create mode 100644 frontend/src/views/Login.tsx create mode 100644 frontend/src/views/Register.tsx create mode 100644 frontend/src/views/SendEmail.tsx create mode 100644 frontend/src/views/admin/UserAdmin.tsx create mode 100644 frontend/src/views/profile/Profile.tsx create mode 100644 frontend/src/views/profile/Security.tsx create mode 100644 frontend/src/views/profile/Textures.tsx create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handle/admin.go create mode 100644 handle/config.go create mode 100644 handle/handelerror/error.go create mode 100644 handle/handle.go create mode 100644 handle/user.go create mode 100644 handle/yggdrasil/error.go create mode 100644 handle/yggdrasil/session.go create mode 100644 handle/yggdrasil/texture.go create mode 100644 handle/yggdrasil/user.go create mode 100644 handle/yggdrasil/yggdrasil.go create mode 100644 main.go create mode 100644 model/const.go create mode 100644 model/model.go create mode 100644 model/yggdrasil/model.go create mode 100644 model/yggdrasil/textures.go create mode 100644 server/provide.go create mode 100644 server/route/middleware.go create mode 100644 server/route/route.go create mode 100644 server/server.go create mode 100644 server/slog.go create mode 100644 server/sqlite_init.go create mode 100644 server/static/static.go create mode 100644 server/wire.go create mode 100644 server/wire_gen.go create mode 100644 service/admin.go create mode 100644 service/admin_test.go create mode 100644 service/auth/auth.go create mode 100644 service/auth/utils.go create mode 100644 service/captcha/captcha.go create mode 100644 service/email/email.go create mode 100644 service/service.go create mode 100644 service/texture.go create mode 100644 service/user.go create mode 100644 service/user_test.go create mode 100644 service/utils/texture.go create mode 100644 service/web.go create mode 100644 service/yggdrasil/session.go create mode 100644 service/yggdrasil/texture.go create mode 100644 service/yggdrasil/user.go create mode 100644 service/yggdrasil/user_test.go create mode 100644 service/yggdrasil/yggdrasil.go create mode 100644 service/yggdrasil/yggdrasil_session_pubkey.der create mode 100644 test/auth_test.go create mode 100644 test/config.toml create mode 100644 test/server_test.go create mode 100644 utils/argon2id.go create mode 100644 utils/decode.go create mode 100644 utils/ip.go create mode 100644 utils/sign/rsa.go create mode 100644 utils/sign/rsa_test.go create mode 100644 utils/tx.go create mode 100644 utils/uuid.go create mode 100644 utils/uuid_test.go diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..5539551 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: + - master + + pull_request: + branches: + - master + +jobs: + deploy: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + # - name: Install Docker + # run: curl -fsSL https://get.docker.com | sh + + - name: Deploy with Docker + run: docker compose up --build --force-recreate -d + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bf30a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +cmd/testserver +cmd/authlibskin/config.yaml +cmd/authlibskin/skin +cmd/authlibskin/authlibskin.exe +server/static/files +cmd/authlibskin/config.toml +out/ +stats.html \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e86f901 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "gopls": { + "buildFlags":["-tags=wireinject,redis,sqlite"] + }, +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0811f36 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM node:20-alpine AS frontend-stage + +WORKDIR /app +RUN corepack enable + +COPY frontend/package.json frontend/pnpm-lock.yaml ./ +RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \ + pnpm install --frozen-lockfile + +COPY frontend . + +RUN pnpm build + +FROM golang:1.23 AS build-stage + +WORKDIR /app +ENV GOPROXY https://goproxy.cn,direct + +COPY --from=frontend-stage /app/dist ./server/static/files +COPY . . + +RUN go build -o app . + +VOLUME /app/config + +ENTRYPOINT ["/app/app", "-c", "./config/config.toml"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9efc233 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 xmdhs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff39aae --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# TinySkin + +Fork from [authlib-skin](https://github.com/xmdhs/authlib-skin). + +轻量级的 Yggdrasil 服务端实现,后端 Go,前端 react。 + +适合于只需要用于 [authlib-injector](https://github.com/yushijinhun/authlib-injector) 的验证服务器的情况,部署简单。 + +实现了 [Yggdrasil](https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83) 规范,可用于一些启动器中的外置登录,和服务器的外置登录。 + +## 特性 + +- 支持用于聊天签名的 /player/certificates 接口 +- 支持于离线模式相同的方式生成用户 uuid(开启后不可更改用户名) +- 基本的用户管理 +- Cloudflare Turnstile 支持 diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..f8cf9a7 --- /dev/null +++ b/config/config.go @@ -0,0 +1,100 @@ +package config + +type Config struct { + OfflineUUID bool `toml:"offlineUUID" comment:"为 true 则 uuid 生成方式于离线模式相同,若从离线模式切换不会丢失数据。\n已有用户数据的情况下勿更改此项"` + Port string `toml:"port"` + Log Log `toml:"log"` + Sql Sql `toml:"sql"` + Debug bool `toml:"debug" comment:"输出每条执行的 sql 语句"` + Cache Cache `toml:"cache"` + RaelIP bool `toml:"raelIP" comment:"位于反向代理后启用,用于记录真实 ip\n若直接提供服务,请勿打开,否则会被伪造 ip"` + MaxIpUser int `toml:"maxIpUser" comment:"ip 段最大注册用户,ipv4 为 /24 ipv6 为 /48"` + RsaPriKey string `toml:"rsaPriKey,multiline" comment:"运行后勿修改,若为集群需设置为一致"` + TexturePath string `toml:"texturePath" comment:"材质文件保存路径,如果需要对象存储可以把对象储存挂载到本地目录上"` + TextureBaseUrl string `toml:"textureBaseUrl" comment:"材质静态文件提供基础地址\n如果静态文件位于 oss 上,比如 https://s3.amazonaws.com/example/1.png\n则填写 https://s3.amazonaws.com/example \n若通过反向代理提供服务并启用了 https,请在在此处填写带有 https 的基础路径,否则游戏内无法加载皮肤"` + WebBaseUrl string `toml:"webBaseUrl" comment:"用于在支持的启动器中展示本站的注册地址\n填写类似 https://example.com"` + ServerName string `toml:"serverName" comment:"皮肤站名字,用于在多个地方展示"` + Captcha Captcha `toml:"captcha"` + Email EmailConfig `toml:"email"` +} + +type Log struct { + Level string `toml:"level"` + Json bool `toml:"json" comment:"json 格式输出"` +} + +type Sql struct { + DriverName string `toml:"driverName" comment:"可填 mysql 或 sqlite3"` + Dsn string `toml:"dsn" comment:"填写见 mysql https://github.com/go-sql-driver/mysql#dsn-data-source-name\nsqlite https://github.com/mattn/go-sqlite3\n例如 mysql 用户名:密码@tcp(mysqlIP:端口)/数据库名\nsqlite data.db?_txlock=IMMEDIATE&_journal_mode=WAL&_fk=true"` +} + +type Cache struct { + Type string `toml:"type" comment:"默认使用内存缓存,若需要集群部署,填写 redis"` + Ram int `toml:"ram" comment:"内存缓存使用大小,单位 b"` + Addr string `toml:"addr" comment:"redis 服务端地址,如 127.0.0.1:6379"` + Password string `toml:"password" comment:"redis 密码"` +} + +type Captcha struct { + Type string `toml:"type" comment:"验证码类型,目前只支持 cloudflare turnstile,若需要填写 turnstile"` + SiteKey string `toml:"siteKey"` + Secret string `toml:"secret"` +} + +type EmailConfig struct { + Enable bool `toml:"enable" comment:"注册验证邮件,且允许使用邮箱找回账号"` + Smtp []SmtpUser `toml:"smtp"` + AllowDomain []string `toml:"allow_domain" comment:"允许用于注册的邮箱域名,留空则允许全部"` + EmailReg string `toml:"email_reg" comment:"邮箱正则,留空则不处理,如 ^[0-9]+@qq.com$|^[^+\\.A-Z]+@gmail.com$"` + EmailRegMsg string `toml:"email_reg_msg" comment:"不满足要求时的提示信息"` +} + +type SmtpUser struct { + Host string `toml:"host"` + Port int `toml:"port"` + SSL bool `toml:"SSL" comment:"启用 ssl"` + Name string `toml:"name"` + Pass string `toml:"password"` +} + +func Default() Config { + return Config{ + OfflineUUID: true, + Port: ":8080", + Log: Log{ + Level: "debug", + Json: false, + }, + Sql: Sql{ + DriverName: "sqlite3", + Dsn: "data.db?_txlock=IMMEDIATE&_journal_mode=WAL&_fk=true", + }, + Debug: false, + Cache: Cache{ + Type: "", + Ram: 10000000, + Addr: "", + Password: "", + }, + RaelIP: false, + MaxIpUser: 0, + RsaPriKey: "", + TexturePath: "", + TextureBaseUrl: "", + WebBaseUrl: "", + ServerName: "没有设置名字", + Captcha: Captcha{}, + Email: EmailConfig{ + Smtp: []SmtpUser{ + { + Host: "", + Port: 0, + SSL: false, + Name: "", + Pass: "", + }, + }, + AllowDomain: []string{}, + }, + } +} diff --git a/db/cache/cache.go b/db/cache/cache.go new file mode 100644 index 0000000..e952760 --- /dev/null +++ b/db/cache/cache.go @@ -0,0 +1,40 @@ +package cache + +import ( + "encoding/json" + "time" +) + +type Cache interface { + Del(k []byte) error + Get(k []byte) ([]byte, error) + Put(k []byte, v []byte, timeOut time.Time) error +} + +type CacheHelp[T any] struct { + Cache +} + +func (c CacheHelp[T]) Get(k []byte) (T, error) { + var t T + b, err := c.Cache.Get(k) + if err != nil { + return t, err + } + if b == nil { + return t, nil + } + err = json.Unmarshal(b, &t) + if err != nil { + return t, err + } + return t, nil +} + +func (c CacheHelp[T]) Put(k []byte, v T, timeOut time.Time) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + return c.Cache.Put(k, b, timeOut) +} diff --git a/db/cache/cache_test.go b/db/cache/cache_test.go new file mode 100644 index 0000000..d227dba --- /dev/null +++ b/db/cache/cache_test.go @@ -0,0 +1,38 @@ +package cache + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCache(t *testing.T) { + cl := []Cache{NewFastCache(100000), NewRedis("127.0.0.1:6379", "")} + + for i, c := range cl { + key := []byte("key") + value := []byte("value") + + require.Nil(t, c.Put(key, value, time.Now().Add(1*time.Hour)), i) + + v, err := c.Get(key) + require.Nil(t, err, i) + + assert.Equal(t, v, value, i) + + require.Nil(t, c.Del(key), i) + + v, err = c.Get(key) + require.Nil(t, err, i) + require.Nil(t, v, i) + + require.Nil(t, c.Put(key, value, time.Now().Add(2*time.Second)), i) + time.Sleep(3 * time.Second) + + v, err = c.Get(key) + require.Nil(t, err, i) + require.Nil(t, v, i) + } +} diff --git a/db/cache/fastcache.go b/db/cache/fastcache.go new file mode 100644 index 0000000..b6c8fb2 --- /dev/null +++ b/db/cache/fastcache.go @@ -0,0 +1,55 @@ +package cache + +import ( + "fmt" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/alecthomas/binary" +) + +var _ Cache = (*FastCache)(nil) + +type FastCache struct { + c *fastcache.Cache +} + +type ttlCache struct { + TimeOut int64 + V []byte +} + +func NewFastCache(maxBytes int) *FastCache { + c := fastcache.New(maxBytes) + return &FastCache{c: c} +} + +func (f *FastCache) Put(k []byte, v []byte, timeOut time.Time) error { + b, err := binary.Marshal(ttlCache{V: v, TimeOut: timeOut.Unix()}) + if err != nil { + return fmt.Errorf("FastCache.Put: %w", err) + } + f.c.SetBig(k, b) + return nil +} + +func (f *FastCache) Del(k []byte) error { + f.c.Del(k) + return nil +} + +func (f *FastCache) Get(k []byte) ([]byte, error) { + b := f.c.GetBig(nil, k) + if b == nil { + return nil, nil + } + me := ttlCache{} + err := binary.Unmarshal(b, &me) + if err != nil { + return nil, fmt.Errorf("FastCache.Get: %w", err) + } + if time.Unix(me.TimeOut, 0).Before(time.Now()) { + return nil, nil + } + return me.V, nil +} diff --git a/db/cache/no_redis.go b/db/cache/no_redis.go new file mode 100644 index 0000000..b2ae414 --- /dev/null +++ b/db/cache/no_redis.go @@ -0,0 +1,7 @@ +//go:build !redis + +package cache + +func NewRedis(addr, pass string) Cache { + panic("not tag redis") +} diff --git a/db/cache/redis.go b/db/cache/redis.go new file mode 100644 index 0000000..de3261f --- /dev/null +++ b/db/cache/redis.go @@ -0,0 +1,53 @@ +//go:build redis + +package cache + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/redis/go-redis/v9" +) + +var _ Cache = (*RedisCache)(nil) + +type RedisCache struct { + c *redis.Client +} + +func NewRedis(addr, pass string) Cache { + rdb := redis.NewClient(&redis.Options{ + Addr: addr, + Password: pass, + }) + return &RedisCache{c: rdb} +} + +func (r *RedisCache) Del(k []byte) error { + _, err := r.c.Del(context.Background(), string(k)).Result() + if err != nil { + return fmt.Errorf("RedisCache.Del: %w", err) + } + return nil +} + +func (r *RedisCache) Get(k []byte) ([]byte, error) { + value, err := r.c.Get(context.Background(), string(k)).Bytes() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, nil + } + return nil, fmt.Errorf("RedisCache.Get: %w", err) + } + return value, nil +} + +func (r *RedisCache) Put(k []byte, v []byte, timeOut time.Time) error { + err := r.c.Set(context.Background(), string(k), v, timeOut.Sub(time.Now())).Err() + if err != nil { + return fmt.Errorf("RedisCache.Put: %w", err) + } + return nil +} diff --git a/db/ent/client.go b/db/ent/client.go new file mode 100644 index 0000000..d03837c --- /dev/null +++ b/db/ent/client.go @@ -0,0 +1,1021 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/xmdhs/authlib-skin/db/ent/migrate" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "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/db/ent/usertexture" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// Client is the client that holds all ent builders. +type Client struct { + config + // Schema is the client for creating, migrating and dropping schema. + Schema *migrate.Schema + // Texture is the client for interacting with the Texture builders. + Texture *TextureClient + // User is the client for interacting with the User builders. + User *UserClient + // UserProfile is the client for interacting with the UserProfile builders. + UserProfile *UserProfileClient + // UserTexture is the client for interacting with the UserTexture builders. + UserTexture *UserTextureClient + // UserToken is the client for interacting with the UserToken builders. + UserToken *UserTokenClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} + cfg.options(opts...) + client := &Client{config: cfg} + client.init() + return client +} + +func (c *Client) init() { + c.Schema = migrate.NewSchema(c.driver) + c.Texture = NewTextureClient(c.config) + c.User = NewUserClient(c.config) + c.UserProfile = NewUserProfileClient(c.config) + c.UserTexture = NewUserTextureClient(c.config) + c.UserToken = NewUserTokenClient(c.config) +} + +type ( + // config is the configuration for the client and its builder. + config struct { + // driver used for executing database requests. + driver dialect.Driver + // debug enable a debug logging. + debug bool + // log used for logging on debug mode. + log func(...any) + // hooks to execute on mutations. + hooks *hooks + // interceptors to execute on queries. + inters *inters + } + // Option function to configure the client. + Option func(*config) +) + +// options applies the options on the config object. +func (c *config) options(opts ...Option) { + for _, opt := range opts { + opt(c) + } + if c.debug { + c.driver = dialect.Debug(c.driver, c.log) + } +} + +// Debug enables debug logging on the ent.Driver. +func Debug() Option { + return func(c *config) { + c.debug = true + } +} + +// Log sets the logging function for debug mode. +func Log(fn func(...any)) Option { + return func(c *config) { + c.log = fn + } +} + +// Driver configures the client driver. +func Driver(driver dialect.Driver) Option { + return func(c *config) { + c.driver = driver + } +} + +// Open opens a database/sql.DB specified by the driver name and +// the data source name, and returns a new client attached to it. +// Optional parameters can be added for configuring the client. +func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { + switch driverName { + case dialect.MySQL, dialect.Postgres, dialect.SQLite: + drv, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return NewClient(append(options, Driver(drv))...), nil + default: + return nil, fmt.Errorf("unsupported driver: %q", driverName) + } +} + +// Tx returns a new transactional client. The provided context +// is used until the transaction is committed or rolled back. +func (c *Client) Tx(ctx context.Context) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := newTx(ctx, c.driver) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = tx + return &Tx{ + ctx: ctx, + config: cfg, + Texture: NewTextureClient(cfg), + User: NewUserClient(cfg), + UserProfile: NewUserProfileClient(cfg), + UserTexture: NewUserTextureClient(cfg), + UserToken: NewUserTokenClient(cfg), + }, nil +} + +// BeginTx returns a transactional client with specified options. +func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := c.driver.(interface { + BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) + }).BeginTx(ctx, opts) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = &txDriver{tx: tx, drv: c.driver} + return &Tx{ + ctx: ctx, + config: cfg, + Texture: NewTextureClient(cfg), + User: NewUserClient(cfg), + UserProfile: NewUserProfileClient(cfg), + UserTexture: NewUserTextureClient(cfg), + UserToken: NewUserTokenClient(cfg), + }, nil +} + +// Debug returns a new debug-client. It's used to get verbose logging on specific operations. +// +// client.Debug(). +// Texture. +// Query(). +// Count(ctx) +func (c *Client) Debug() *Client { + if c.debug { + return c + } + cfg := c.config + cfg.driver = dialect.Debug(c.driver, c.log) + client := &Client{config: cfg} + client.init() + return client +} + +// Close closes the database connection and prevents new queries from starting. +func (c *Client) Close() error { + return c.driver.Close() +} + +// Use adds the mutation hooks to all the entity clients. +// In order to add hooks to a specific client, call: `client.Node.Use(...)`. +func (c *Client) Use(hooks ...Hook) { + c.Texture.Use(hooks...) + c.User.Use(hooks...) + c.UserProfile.Use(hooks...) + c.UserTexture.Use(hooks...) + c.UserToken.Use(hooks...) +} + +// Intercept adds the query interceptors to all the entity clients. +// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. +func (c *Client) Intercept(interceptors ...Interceptor) { + c.Texture.Intercept(interceptors...) + c.User.Intercept(interceptors...) + c.UserProfile.Intercept(interceptors...) + c.UserTexture.Intercept(interceptors...) + c.UserToken.Intercept(interceptors...) +} + +// Mutate implements the ent.Mutator interface. +func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { + switch m := m.(type) { + case *TextureMutation: + return c.Texture.mutate(ctx, m) + case *UserMutation: + return c.User.mutate(ctx, m) + case *UserProfileMutation: + return c.UserProfile.mutate(ctx, m) + case *UserTextureMutation: + return c.UserTexture.mutate(ctx, m) + case *UserTokenMutation: + return c.UserToken.mutate(ctx, m) + default: + return nil, fmt.Errorf("ent: unknown mutation type %T", m) + } +} + +// TextureClient is a client for the Texture schema. +type TextureClient struct { + config +} + +// NewTextureClient returns a client for the Texture from the given config. +func NewTextureClient(c config) *TextureClient { + return &TextureClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `texture.Hooks(f(g(h())))`. +func (c *TextureClient) Use(hooks ...Hook) { + c.hooks.Texture = append(c.hooks.Texture, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `texture.Intercept(f(g(h())))`. +func (c *TextureClient) Intercept(interceptors ...Interceptor) { + c.inters.Texture = append(c.inters.Texture, interceptors...) +} + +// Create returns a builder for creating a Texture entity. +func (c *TextureClient) Create() *TextureCreate { + mutation := newTextureMutation(c.config, OpCreate) + return &TextureCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Texture entities. +func (c *TextureClient) CreateBulk(builders ...*TextureCreate) *TextureCreateBulk { + return &TextureCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Texture. +func (c *TextureClient) Update() *TextureUpdate { + mutation := newTextureMutation(c.config, OpUpdate) + return &TextureUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *TextureClient) UpdateOne(t *Texture) *TextureUpdateOne { + mutation := newTextureMutation(c.config, OpUpdateOne, withTexture(t)) + return &TextureUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *TextureClient) UpdateOneID(id int) *TextureUpdateOne { + mutation := newTextureMutation(c.config, OpUpdateOne, withTextureID(id)) + return &TextureUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Texture. +func (c *TextureClient) Delete() *TextureDelete { + mutation := newTextureMutation(c.config, OpDelete) + return &TextureDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *TextureClient) DeleteOne(t *Texture) *TextureDeleteOne { + return c.DeleteOneID(t.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *TextureClient) DeleteOneID(id int) *TextureDeleteOne { + builder := c.Delete().Where(texture.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &TextureDeleteOne{builder} +} + +// Query returns a query builder for Texture. +func (c *TextureClient) Query() *TextureQuery { + return &TextureQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeTexture}, + inters: c.Interceptors(), + } +} + +// Get returns a Texture entity by its id. +func (c *TextureClient) Get(ctx context.Context, id int) (*Texture, error) { + return c.Query().Where(texture.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *TextureClient) GetX(ctx context.Context, id int) *Texture { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryCreatedUser queries the created_user edge of a Texture. +func (c *TextureClient) QueryCreatedUser(t *Texture) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := t.ID + step := sqlgraph.NewStep( + sqlgraph.From(texture.Table, texture.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, texture.CreatedUserTable, texture.CreatedUserColumn), + ) + fromV = sqlgraph.Neighbors(t.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryUserProfile queries the user_profile edge of a Texture. +func (c *TextureClient) QueryUserProfile(t *Texture) *UserProfileQuery { + query := (&UserProfileClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := t.ID + step := sqlgraph.NewStep( + sqlgraph.From(texture.Table, texture.FieldID, id), + sqlgraph.To(userprofile.Table, userprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, texture.UserProfileTable, texture.UserProfilePrimaryKey...), + ) + fromV = sqlgraph.Neighbors(t.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryUsertexture queries the usertexture edge of a Texture. +func (c *TextureClient) QueryUsertexture(t *Texture) *UserTextureQuery { + query := (&UserTextureClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := t.ID + step := sqlgraph.NewStep( + sqlgraph.From(texture.Table, texture.FieldID, id), + sqlgraph.To(usertexture.Table, usertexture.FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, texture.UsertextureTable, texture.UsertextureColumn), + ) + fromV = sqlgraph.Neighbors(t.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *TextureClient) Hooks() []Hook { + return c.hooks.Texture +} + +// Interceptors returns the client interceptors. +func (c *TextureClient) Interceptors() []Interceptor { + return c.inters.Texture +} + +func (c *TextureClient) mutate(ctx context.Context, m *TextureMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&TextureCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&TextureUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&TextureUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&TextureDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Texture mutation op: %q", m.Op()) + } +} + +// UserClient is a client for the User schema. +type UserClient struct { + config +} + +// NewUserClient returns a client for the User from the given config. +func NewUserClient(c config) *UserClient { + return &UserClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `user.Hooks(f(g(h())))`. +func (c *UserClient) Use(hooks ...Hook) { + c.hooks.User = append(c.hooks.User, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `user.Intercept(f(g(h())))`. +func (c *UserClient) Intercept(interceptors ...Interceptor) { + c.inters.User = append(c.inters.User, interceptors...) +} + +// Create returns a builder for creating a User entity. +func (c *UserClient) Create() *UserCreate { + mutation := newUserMutation(c.config, OpCreate) + return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of User entities. +func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk { + return &UserCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for User. +func (c *UserClient) Update() *UserUpdate { + mutation := newUserMutation(c.config, OpUpdate) + return &UserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserClient) UpdateOne(u *User) *UserUpdateOne { + mutation := newUserMutation(c.config, OpUpdateOne, withUser(u)) + return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserClient) UpdateOneID(id int) *UserUpdateOne { + mutation := newUserMutation(c.config, OpUpdateOne, withUserID(id)) + return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for User. +func (c *UserClient) Delete() *UserDelete { + mutation := newUserMutation(c.config, OpDelete) + return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserClient) DeleteOne(u *User) *UserDeleteOne { + return c.DeleteOneID(u.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserClient) DeleteOneID(id int) *UserDeleteOne { + builder := c.Delete().Where(user.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserDeleteOne{builder} +} + +// Query returns a query builder for User. +func (c *UserClient) Query() *UserQuery { + return &UserQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUser}, + inters: c.Interceptors(), + } +} + +// Get returns a User entity by its id. +func (c *UserClient) Get(ctx context.Context, id int) (*User, error) { + return c.Query().Where(user.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserClient) GetX(ctx context.Context, id int) *User { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryCreatedTexture queries the created_texture edge of a User. +func (c *UserClient) QueryCreatedTexture(u *User) *TextureQuery { + query := (&TextureClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(texture.Table, texture.FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, user.CreatedTextureTable, user.CreatedTextureColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryProfile queries the profile edge of a User. +func (c *UserClient) QueryProfile(u *User) *UserProfileQuery { + query := (&UserProfileClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(userprofile.Table, userprofile.FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, user.ProfileTable, user.ProfileColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryToken queries the token edge of a User. +func (c *UserClient) QueryToken(u *User) *UserTokenQuery { + query := (&UserTokenClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(usertoken.Table, usertoken.FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, user.TokenTable, user.TokenColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserClient) Hooks() []Hook { + return c.hooks.User +} + +// Interceptors returns the client interceptors. +func (c *UserClient) Interceptors() []Interceptor { + return c.inters.User +} + +func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown User mutation op: %q", m.Op()) + } +} + +// UserProfileClient is a client for the UserProfile schema. +type UserProfileClient struct { + config +} + +// NewUserProfileClient returns a client for the UserProfile from the given config. +func NewUserProfileClient(c config) *UserProfileClient { + return &UserProfileClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `userprofile.Hooks(f(g(h())))`. +func (c *UserProfileClient) Use(hooks ...Hook) { + c.hooks.UserProfile = append(c.hooks.UserProfile, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `userprofile.Intercept(f(g(h())))`. +func (c *UserProfileClient) Intercept(interceptors ...Interceptor) { + c.inters.UserProfile = append(c.inters.UserProfile, interceptors...) +} + +// Create returns a builder for creating a UserProfile entity. +func (c *UserProfileClient) Create() *UserProfileCreate { + mutation := newUserProfileMutation(c.config, OpCreate) + return &UserProfileCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserProfile entities. +func (c *UserProfileClient) CreateBulk(builders ...*UserProfileCreate) *UserProfileCreateBulk { + return &UserProfileCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserProfile. +func (c *UserProfileClient) Update() *UserProfileUpdate { + mutation := newUserProfileMutation(c.config, OpUpdate) + return &UserProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserProfileClient) UpdateOne(up *UserProfile) *UserProfileUpdateOne { + mutation := newUserProfileMutation(c.config, OpUpdateOne, withUserProfile(up)) + return &UserProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserProfileClient) UpdateOneID(id int) *UserProfileUpdateOne { + mutation := newUserProfileMutation(c.config, OpUpdateOne, withUserProfileID(id)) + return &UserProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserProfile. +func (c *UserProfileClient) Delete() *UserProfileDelete { + mutation := newUserProfileMutation(c.config, OpDelete) + return &UserProfileDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserProfileClient) DeleteOne(up *UserProfile) *UserProfileDeleteOne { + return c.DeleteOneID(up.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserProfileClient) DeleteOneID(id int) *UserProfileDeleteOne { + builder := c.Delete().Where(userprofile.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserProfileDeleteOne{builder} +} + +// Query returns a query builder for UserProfile. +func (c *UserProfileClient) Query() *UserProfileQuery { + return &UserProfileQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserProfile}, + inters: c.Interceptors(), + } +} + +// Get returns a UserProfile entity by its id. +func (c *UserProfileClient) Get(ctx context.Context, id int) (*UserProfile, error) { + return c.Query().Where(userprofile.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserProfileClient) GetX(ctx context.Context, id int) *UserProfile { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a UserProfile. +func (c *UserProfileClient) QueryUser(up *UserProfile) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := up.ID + step := sqlgraph.NewStep( + sqlgraph.From(userprofile.Table, userprofile.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, userprofile.UserTable, userprofile.UserColumn), + ) + fromV = sqlgraph.Neighbors(up.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryTexture queries the texture edge of a UserProfile. +func (c *UserProfileClient) QueryTexture(up *UserProfile) *TextureQuery { + query := (&TextureClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := up.ID + step := sqlgraph.NewStep( + sqlgraph.From(userprofile.Table, userprofile.FieldID, id), + sqlgraph.To(texture.Table, texture.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, userprofile.TextureTable, userprofile.TexturePrimaryKey...), + ) + fromV = sqlgraph.Neighbors(up.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryUsertexture queries the usertexture edge of a UserProfile. +func (c *UserProfileClient) QueryUsertexture(up *UserProfile) *UserTextureQuery { + query := (&UserTextureClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := up.ID + step := sqlgraph.NewStep( + sqlgraph.From(userprofile.Table, userprofile.FieldID, id), + sqlgraph.To(usertexture.Table, usertexture.FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, userprofile.UsertextureTable, userprofile.UsertextureColumn), + ) + fromV = sqlgraph.Neighbors(up.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserProfileClient) Hooks() []Hook { + return c.hooks.UserProfile +} + +// Interceptors returns the client interceptors. +func (c *UserProfileClient) Interceptors() []Interceptor { + return c.inters.UserProfile +} + +func (c *UserProfileClient) mutate(ctx context.Context, m *UserProfileMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserProfileCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserProfileDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown UserProfile mutation op: %q", m.Op()) + } +} + +// UserTextureClient is a client for the UserTexture schema. +type UserTextureClient struct { + config +} + +// NewUserTextureClient returns a client for the UserTexture from the given config. +func NewUserTextureClient(c config) *UserTextureClient { + return &UserTextureClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `usertexture.Hooks(f(g(h())))`. +func (c *UserTextureClient) Use(hooks ...Hook) { + c.hooks.UserTexture = append(c.hooks.UserTexture, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `usertexture.Intercept(f(g(h())))`. +func (c *UserTextureClient) Intercept(interceptors ...Interceptor) { + c.inters.UserTexture = append(c.inters.UserTexture, interceptors...) +} + +// Create returns a builder for creating a UserTexture entity. +func (c *UserTextureClient) Create() *UserTextureCreate { + mutation := newUserTextureMutation(c.config, OpCreate) + return &UserTextureCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserTexture entities. +func (c *UserTextureClient) CreateBulk(builders ...*UserTextureCreate) *UserTextureCreateBulk { + return &UserTextureCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserTexture. +func (c *UserTextureClient) Update() *UserTextureUpdate { + mutation := newUserTextureMutation(c.config, OpUpdate) + return &UserTextureUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserTextureClient) UpdateOne(ut *UserTexture) *UserTextureUpdateOne { + mutation := newUserTextureMutation(c.config, OpUpdateOne, withUserTexture(ut)) + return &UserTextureUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserTextureClient) UpdateOneID(id int) *UserTextureUpdateOne { + mutation := newUserTextureMutation(c.config, OpUpdateOne, withUserTextureID(id)) + return &UserTextureUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserTexture. +func (c *UserTextureClient) Delete() *UserTextureDelete { + mutation := newUserTextureMutation(c.config, OpDelete) + return &UserTextureDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserTextureClient) DeleteOne(ut *UserTexture) *UserTextureDeleteOne { + return c.DeleteOneID(ut.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserTextureClient) DeleteOneID(id int) *UserTextureDeleteOne { + builder := c.Delete().Where(usertexture.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserTextureDeleteOne{builder} +} + +// Query returns a query builder for UserTexture. +func (c *UserTextureClient) Query() *UserTextureQuery { + return &UserTextureQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserTexture}, + inters: c.Interceptors(), + } +} + +// Get returns a UserTexture entity by its id. +func (c *UserTextureClient) Get(ctx context.Context, id int) (*UserTexture, error) { + return c.Query().Where(usertexture.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserTextureClient) GetX(ctx context.Context, id int) *UserTexture { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUserProfile queries the user_profile edge of a UserTexture. +func (c *UserTextureClient) QueryUserProfile(ut *UserTexture) *UserProfileQuery { + query := (&UserProfileClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := ut.ID + step := sqlgraph.NewStep( + sqlgraph.From(usertexture.Table, usertexture.FieldID, id), + sqlgraph.To(userprofile.Table, userprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, usertexture.UserProfileTable, usertexture.UserProfileColumn), + ) + fromV = sqlgraph.Neighbors(ut.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryTexture queries the texture edge of a UserTexture. +func (c *UserTextureClient) QueryTexture(ut *UserTexture) *TextureQuery { + query := (&TextureClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := ut.ID + step := sqlgraph.NewStep( + sqlgraph.From(usertexture.Table, usertexture.FieldID, id), + sqlgraph.To(texture.Table, texture.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, usertexture.TextureTable, usertexture.TextureColumn), + ) + fromV = sqlgraph.Neighbors(ut.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserTextureClient) Hooks() []Hook { + return c.hooks.UserTexture +} + +// Interceptors returns the client interceptors. +func (c *UserTextureClient) Interceptors() []Interceptor { + return c.inters.UserTexture +} + +func (c *UserTextureClient) mutate(ctx context.Context, m *UserTextureMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserTextureCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserTextureUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserTextureUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserTextureDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown UserTexture mutation op: %q", m.Op()) + } +} + +// UserTokenClient is a client for the UserToken schema. +type UserTokenClient struct { + config +} + +// NewUserTokenClient returns a client for the UserToken from the given config. +func NewUserTokenClient(c config) *UserTokenClient { + return &UserTokenClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `usertoken.Hooks(f(g(h())))`. +func (c *UserTokenClient) Use(hooks ...Hook) { + c.hooks.UserToken = append(c.hooks.UserToken, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `usertoken.Intercept(f(g(h())))`. +func (c *UserTokenClient) Intercept(interceptors ...Interceptor) { + c.inters.UserToken = append(c.inters.UserToken, interceptors...) +} + +// Create returns a builder for creating a UserToken entity. +func (c *UserTokenClient) Create() *UserTokenCreate { + mutation := newUserTokenMutation(c.config, OpCreate) + return &UserTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserToken entities. +func (c *UserTokenClient) CreateBulk(builders ...*UserTokenCreate) *UserTokenCreateBulk { + return &UserTokenCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserToken. +func (c *UserTokenClient) Update() *UserTokenUpdate { + mutation := newUserTokenMutation(c.config, OpUpdate) + return &UserTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserTokenClient) UpdateOne(ut *UserToken) *UserTokenUpdateOne { + mutation := newUserTokenMutation(c.config, OpUpdateOne, withUserToken(ut)) + return &UserTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserTokenClient) UpdateOneID(id int) *UserTokenUpdateOne { + mutation := newUserTokenMutation(c.config, OpUpdateOne, withUserTokenID(id)) + return &UserTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserToken. +func (c *UserTokenClient) Delete() *UserTokenDelete { + mutation := newUserTokenMutation(c.config, OpDelete) + return &UserTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserTokenClient) DeleteOne(ut *UserToken) *UserTokenDeleteOne { + return c.DeleteOneID(ut.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserTokenClient) DeleteOneID(id int) *UserTokenDeleteOne { + builder := c.Delete().Where(usertoken.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserTokenDeleteOne{builder} +} + +// Query returns a query builder for UserToken. +func (c *UserTokenClient) Query() *UserTokenQuery { + return &UserTokenQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserToken}, + inters: c.Interceptors(), + } +} + +// Get returns a UserToken entity by its id. +func (c *UserTokenClient) Get(ctx context.Context, id int) (*UserToken, error) { + return c.Query().Where(usertoken.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserTokenClient) GetX(ctx context.Context, id int) *UserToken { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a UserToken. +func (c *UserTokenClient) QueryUser(ut *UserToken) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := ut.ID + step := sqlgraph.NewStep( + sqlgraph.From(usertoken.Table, usertoken.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, usertoken.UserTable, usertoken.UserColumn), + ) + fromV = sqlgraph.Neighbors(ut.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserTokenClient) Hooks() []Hook { + return c.hooks.UserToken +} + +// Interceptors returns the client interceptors. +func (c *UserTokenClient) Interceptors() []Interceptor { + return c.inters.UserToken +} + +func (c *UserTokenClient) mutate(ctx context.Context, m *UserTokenMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown UserToken mutation op: %q", m.Op()) + } +} + +// hooks and interceptors per client, for fast access. +type ( + hooks struct { + Texture, User, UserProfile, UserTexture, UserToken []ent.Hook + } + inters struct { + Texture, User, UserProfile, UserTexture, UserToken []ent.Interceptor + } +) diff --git a/db/ent/ent.go b/db/ent/ent.go new file mode 100644 index 0000000..8ef748e --- /dev/null +++ b/db/ent/ent.go @@ -0,0 +1,616 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "reflect" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "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/db/ent/usertexture" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// ent aliases to avoid import conflicts in user's code. +type ( + Op = ent.Op + Hook = ent.Hook + Value = ent.Value + Query = ent.Query + QueryContext = ent.QueryContext + Querier = ent.Querier + QuerierFunc = ent.QuerierFunc + Interceptor = ent.Interceptor + InterceptFunc = ent.InterceptFunc + Traverser = ent.Traverser + TraverseFunc = ent.TraverseFunc + Policy = ent.Policy + Mutator = ent.Mutator + Mutation = ent.Mutation + MutateFunc = ent.MutateFunc +) + +type clientCtxKey struct{} + +// FromContext returns a Client stored inside a context, or nil if there isn't one. +func FromContext(ctx context.Context) *Client { + c, _ := ctx.Value(clientCtxKey{}).(*Client) + return c +} + +// NewContext returns a new context with the given Client attached. +func NewContext(parent context.Context, c *Client) context.Context { + return context.WithValue(parent, clientCtxKey{}, c) +} + +type txCtxKey struct{} + +// TxFromContext returns a Tx stored inside a context, or nil if there isn't one. +func TxFromContext(ctx context.Context) *Tx { + tx, _ := ctx.Value(txCtxKey{}).(*Tx) + return tx +} + +// NewTxContext returns a new context with the given Tx attached. +func NewTxContext(parent context.Context, tx *Tx) context.Context { + return context.WithValue(parent, txCtxKey{}, tx) +} + +// OrderFunc applies an ordering on the sql selector. +// Deprecated: Use Asc/Desc functions or the package builders instead. +type OrderFunc func(*sql.Selector) + +var ( + initCheck sync.Once + columnCheck sql.ColumnCheck +) + +// columnChecker checks if the column exists in the given table. +func checkColumn(table, column string) error { + initCheck.Do(func() { + columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + texture.Table: texture.ValidColumn, + user.Table: user.ValidColumn, + userprofile.Table: userprofile.ValidColumn, + usertexture.Table: usertexture.ValidColumn, + usertoken.Table: usertoken.ValidColumn, + }) + }) + return columnCheck(table, column) +} + +// Asc applies the given fields in ASC order. +func Asc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) + } + s.OrderBy(sql.Asc(s.C(f))) + } + } +} + +// Desc applies the given fields in DESC order. +func Desc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) + } + s.OrderBy(sql.Desc(s.C(f))) + } + } +} + +// AggregateFunc applies an aggregation step on the group-by traversal/selector. +type AggregateFunc func(*sql.Selector) string + +// As is a pseudo aggregation function for renaming another other functions with custom names. For example: +// +// GroupBy(field1, field2). +// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")). +// Scan(ctx, &v) +func As(fn AggregateFunc, end string) AggregateFunc { + return func(s *sql.Selector) string { + return sql.As(fn(s), end) + } +} + +// Count applies the "count" aggregation function on each group. +func Count() AggregateFunc { + return func(s *sql.Selector) string { + return sql.Count("*") + } +} + +// Max applies the "max" aggregation function on the given field of each group. +func Max(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Max(s.C(field)) + } +} + +// Mean applies the "mean" aggregation function on the given field of each group. +func Mean(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Avg(s.C(field)) + } +} + +// Min applies the "min" aggregation function on the given field of each group. +func Min(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Min(s.C(field)) + } +} + +// Sum applies the "sum" aggregation function on the given field of each group. +func Sum(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Sum(s.C(field)) + } +} + +// ValidationError returns when validating a field or edge fails. +type ValidationError struct { + Name string // Field or edge name. + err error +} + +// Error implements the error interface. +func (e *ValidationError) Error() string { + return e.err.Error() +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ValidationError) Unwrap() error { + return e.err +} + +// IsValidationError returns a boolean indicating whether the error is a validation error. +func IsValidationError(err error) bool { + if err == nil { + return false + } + var e *ValidationError + return errors.As(err, &e) +} + +// NotFoundError returns when trying to fetch a specific entity and it was not found in the database. +type NotFoundError struct { + label string +} + +// Error implements the error interface. +func (e *NotFoundError) Error() string { + return "ent: " + e.label + " not found" +} + +// IsNotFound returns a boolean indicating whether the error is a not found error. +func IsNotFound(err error) bool { + if err == nil { + return false + } + var e *NotFoundError + return errors.As(err, &e) +} + +// MaskNotFound masks not found error. +func MaskNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + +// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. +type NotSingularError struct { + label string +} + +// Error implements the error interface. +func (e *NotSingularError) Error() string { + return "ent: " + e.label + " not singular" +} + +// IsNotSingular returns a boolean indicating whether the error is a not singular error. +func IsNotSingular(err error) bool { + if err == nil { + return false + } + var e *NotSingularError + return errors.As(err, &e) +} + +// NotLoadedError returns when trying to get a node that was not loaded by the query. +type NotLoadedError struct { + edge string +} + +// Error implements the error interface. +func (e *NotLoadedError) Error() string { + return "ent: " + e.edge + " edge was not loaded" +} + +// IsNotLoaded returns a boolean indicating whether the error is a not loaded error. +func IsNotLoaded(err error) bool { + if err == nil { + return false + } + var e *NotLoadedError + return errors.As(err, &e) +} + +// ConstraintError returns when trying to create/update one or more entities and +// one or more of their constraints failed. For example, violation of edge or +// field uniqueness. +type ConstraintError struct { + msg string + wrap error +} + +// Error implements the error interface. +func (e ConstraintError) Error() string { + return "ent: constraint failed: " + e.msg +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ConstraintError) Unwrap() error { + return e.wrap +} + +// IsConstraintError returns a boolean indicating whether the error is a constraint failure. +func IsConstraintError(err error) bool { + if err == nil { + return false + } + var e *ConstraintError + return errors.As(err, &e) +} + +// selector embedded by the different Select/GroupBy builders. +type selector struct { + label string + flds *[]string + fns []AggregateFunc + scan func(context.Context, any) error +} + +// ScanX is like Scan, but panics if an error occurs. +func (s *selector) ScanX(ctx context.Context, v any) { + if err := s.scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from a selector. It is only allowed when selecting one field. +func (s *selector) Strings(ctx context.Context) ([]string, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Strings is not achievable when selecting more than 1 field") + } + var v []string + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (s *selector) StringsX(ctx context.Context) []string { + v, err := s.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// String returns a single string from a selector. It is only allowed when selecting one field. +func (s *selector) String(ctx context.Context) (_ string, err error) { + var v []string + if v, err = s.Strings(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Strings returned %d results when one was expected", len(v)) + } + return +} + +// StringX is like String, but panics if an error occurs. +func (s *selector) StringX(ctx context.Context) string { + v, err := s.String(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from a selector. It is only allowed when selecting one field. +func (s *selector) Ints(ctx context.Context) ([]int, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Ints is not achievable when selecting more than 1 field") + } + var v []int + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (s *selector) IntsX(ctx context.Context) []int { + v, err := s.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Int returns a single int from a selector. It is only allowed when selecting one field. +func (s *selector) Int(ctx context.Context) (_ int, err error) { + var v []int + if v, err = s.Ints(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Ints returned %d results when one was expected", len(v)) + } + return +} + +// IntX is like Int, but panics if an error occurs. +func (s *selector) IntX(ctx context.Context) int { + v, err := s.Int(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from a selector. It is only allowed when selecting one field. +func (s *selector) Float64s(ctx context.Context) ([]float64, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Float64s is not achievable when selecting more than 1 field") + } + var v []float64 + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (s *selector) Float64sX(ctx context.Context) []float64 { + v, err := s.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64 returns a single float64 from a selector. It is only allowed when selecting one field. +func (s *selector) Float64(ctx context.Context) (_ float64, err error) { + var v []float64 + if v, err = s.Float64s(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Float64s returned %d results when one was expected", len(v)) + } + return +} + +// Float64X is like Float64, but panics if an error occurs. +func (s *selector) Float64X(ctx context.Context) float64 { + v, err := s.Float64(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from a selector. It is only allowed when selecting one field. +func (s *selector) Bools(ctx context.Context) ([]bool, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Bools is not achievable when selecting more than 1 field") + } + var v []bool + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (s *selector) BoolsX(ctx context.Context) []bool { + v, err := s.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bool returns a single bool from a selector. It is only allowed when selecting one field. +func (s *selector) Bool(ctx context.Context) (_ bool, err error) { + var v []bool + if v, err = s.Bools(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Bools returned %d results when one was expected", len(v)) + } + return +} + +// BoolX is like Bool, but panics if an error occurs. +func (s *selector) BoolX(ctx context.Context) bool { + v, err := s.Bool(ctx) + if err != nil { + panic(err) + } + return v +} + +// withHooks invokes the builder operation with the given hooks, if any. +func withHooks[V Value, M any, PM interface { + *M + Mutation +}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) { + if len(hooks) == 0 { + return exec(ctx) + } + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutationT, ok := any(m).(PM) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + // Set the mutation to the builder. + *mutation = *mutationT + return exec(ctx) + }) + for i := len(hooks) - 1; i >= 0; i-- { + if hooks[i] == nil { + return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = hooks[i](mut) + } + v, err := mut.Mutate(ctx, mutation) + if err != nil { + return value, err + } + nv, ok := v.(V) + if !ok { + return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation) + } + return nv, nil +} + +// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist. +func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context { + if ent.QueryFromContext(ctx) == nil { + qc.Op = op + ctx = ent.NewQueryContext(ctx, qc) + } + return ctx +} + +func querierAll[V Value, Q interface { + sqlAll(context.Context, ...queryHook) (V, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlAll(ctx) + }) +} + +func querierCount[Q interface { + sqlCount(context.Context) (int, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlCount(ctx) + }) +} + +func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) { + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + rv, err := qr.Query(ctx, q) + if err != nil { + return v, err + } + vt, ok := rv.(V) + if !ok { + return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v) + } + return vt, nil +} + +func scanWithInterceptors[Q1 ent.Query, Q2 interface { + sqlScan(context.Context, Q1, any) error +}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error { + rv := reflect.ValueOf(v) + var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q1) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + if err := selectOrGroup.sqlScan(ctx, query, v); err != nil { + return nil, err + } + if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() { + return rv.Elem().Interface(), nil + } + return v, nil + }) + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + vv, err := qr.Query(ctx, rootQuery) + if err != nil { + return err + } + switch rv2 := reflect.ValueOf(vv); { + case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer: + case rv.Type() == rv2.Type(): + rv.Elem().Set(rv2.Elem()) + case rv.Elem().Type() == rv2.Type(): + rv.Elem().Set(rv2) + } + return nil +} + +// queryHook describes an internal hook for the different sqlAll methods. +type queryHook func(context.Context, *sqlgraph.QuerySpec) diff --git a/db/ent/enttest/enttest.go b/db/ent/enttest/enttest.go new file mode 100644 index 0000000..2cb4e57 --- /dev/null +++ b/db/ent/enttest/enttest.go @@ -0,0 +1,84 @@ +// Code generated by ent, DO NOT EDIT. + +package enttest + +import ( + "context" + + "github.com/xmdhs/authlib-skin/db/ent" + // required by schema hooks. + _ "github.com/xmdhs/authlib-skin/db/ent/runtime" + + "entgo.io/ent/dialect/sql/schema" + "github.com/xmdhs/authlib-skin/db/ent/migrate" +) + +type ( + // TestingT is the interface that is shared between + // testing.T and testing.B and used by enttest. + TestingT interface { + FailNow() + Error(...any) + } + + // Option configures client creation. + Option func(*options) + + options struct { + opts []ent.Option + migrateOpts []schema.MigrateOption + } +) + +// WithOptions forwards options to client creation. +func WithOptions(opts ...ent.Option) Option { + return func(o *options) { + o.opts = append(o.opts, opts...) + } +} + +// WithMigrateOptions forwards options to auto migration. +func WithMigrateOptions(opts ...schema.MigrateOption) Option { + return func(o *options) { + o.migrateOpts = append(o.migrateOpts, opts...) + } +} + +func newOptions(opts []Option) *options { + o := &options{} + for _, opt := range opts { + opt(o) + } + return o +} + +// Open calls ent.Open and auto-run migration. +func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { + o := newOptions(opts) + c, err := ent.Open(driverName, dataSourceName, o.opts...) + if err != nil { + t.Error(err) + t.FailNow() + } + migrateSchema(t, c, o) + return c +} + +// NewClient calls ent.NewClient and auto-run migration. +func NewClient(t TestingT, opts ...Option) *ent.Client { + o := newOptions(opts) + c := ent.NewClient(o.opts...) + migrateSchema(t, c, o) + return c +} +func migrateSchema(t TestingT, c *ent.Client, o *options) { + tables, err := schema.CopyTables(migrate.Tables) + if err != nil { + t.Error(err) + t.FailNow() + } + if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { + t.Error(err) + t.FailNow() + } +} diff --git a/db/ent/for_update.tmpl b/db/ent/for_update.tmpl new file mode 100644 index 0000000..1507d0c --- /dev/null +++ b/db/ent/for_update.tmpl @@ -0,0 +1,40 @@ +{{/* +Copyright 2019-present Facebook Inc. All rights reserved. +This source code is licensed under the Apache 2.0 license found +in the LICENSE file in the root directory of this source tree. +*/}} + +{{/* gotype: entgo.io/ent/entc/gen.Type */}} + +{{/* Templates used by the "sql/lock" feature-flag to add "SELECT ... FOR UPDATE/SHARE" capabilities. */}} + +{{ define "dialect/sql/query/additional/locking_sqlite" }} + {{ if $.FeatureEnabled "sql/lock" }} + {{ template "helper/sqlock_sqlite" $ }} + {{ end }} +{{ end }} + +{{ define "helper/sqlock_sqlite" }} +{{ $builder := pascal $.Scope.Builder }} +{{ $receiver := receiver $builder }} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func ({{ $receiver }} *{{ $builder }}) ForUpdateA(opts ...sql.LockOption) *{{ $builder }} { + if {{ $receiver }}.driver.Dialect() == dialect.SQLite { + return {{ $receiver }} + } + return {{ $receiver }}.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func ({{ $receiver }} *{{ $builder }}) ForShareA(opts ...sql.LockOption) *{{ $builder }} { + if {{ $receiver }}.driver.Dialect() == dialect.SQLite { + return {{ $receiver }} + } + return {{ $receiver }}.ForShare(opts...) +} +{{ end }} \ No newline at end of file diff --git a/db/ent/generate.go b/db/ent/generate.go new file mode 100644 index 0000000..6dc7a9e --- /dev/null +++ b/db/ent/generate.go @@ -0,0 +1,3 @@ +package ent + +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/lock --template for_update.tmpl ./schema diff --git a/db/ent/hook/hook.go b/db/ent/hook/hook.go new file mode 100644 index 0000000..b19ae4a --- /dev/null +++ b/db/ent/hook/hook.go @@ -0,0 +1,247 @@ +// Code generated by ent, DO NOT EDIT. + +package hook + +import ( + "context" + "fmt" + + "github.com/xmdhs/authlib-skin/db/ent" +) + +// The TextureFunc type is an adapter to allow the use of ordinary +// function as Texture mutator. +type TextureFunc func(context.Context, *ent.TextureMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f TextureFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.TextureMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TextureMutation", m) +} + +// The UserFunc type is an adapter to allow the use of ordinary +// function as User mutator. +type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.UserMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m) +} + +// The UserProfileFunc type is an adapter to allow the use of ordinary +// function as UserProfile mutator. +type UserProfileFunc func(context.Context, *ent.UserProfileMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f UserProfileFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.UserProfileMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserProfileMutation", m) +} + +// The UserTextureFunc type is an adapter to allow the use of ordinary +// function as UserTexture mutator. +type UserTextureFunc func(context.Context, *ent.UserTextureMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f UserTextureFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.UserTextureMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserTextureMutation", m) +} + +// The UserTokenFunc type is an adapter to allow the use of ordinary +// function as UserToken mutator. +type UserTokenFunc func(context.Context, *ent.UserTokenMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f UserTokenFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.UserTokenMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserTokenMutation", m) +} + +// Condition is a hook condition function. +type Condition func(context.Context, ent.Mutation) bool + +// And groups conditions with the AND operator. +func And(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if !first(ctx, m) || !second(ctx, m) { + return false + } + for _, cond := range rest { + if !cond(ctx, m) { + return false + } + } + return true + } +} + +// Or groups conditions with the OR operator. +func Or(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if first(ctx, m) || second(ctx, m) { + return true + } + for _, cond := range rest { + if cond(ctx, m) { + return true + } + } + return false + } +} + +// Not negates a given condition. +func Not(cond Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + return !cond(ctx, m) + } +} + +// HasOp is a condition testing mutation operation. +func HasOp(op ent.Op) Condition { + return func(_ context.Context, m ent.Mutation) bool { + return m.Op().Is(op) + } +} + +// HasAddedFields is a condition validating `.AddedField` on fields. +func HasAddedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.AddedField(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.AddedField(field); !exists { + return false + } + } + return true + } +} + +// HasClearedFields is a condition validating `.FieldCleared` on fields. +func HasClearedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if exists := m.FieldCleared(field); !exists { + return false + } + for _, field := range fields { + if exists := m.FieldCleared(field); !exists { + return false + } + } + return true + } +} + +// HasFields is a condition validating `.Field` on fields. +func HasFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.Field(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.Field(field); !exists { + return false + } + } + return true + } +} + +// If executes the given hook under condition. +// +// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) +func If(hk ent.Hook, cond Condition) ent.Hook { + return func(next ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if cond(ctx, m) { + return hk(next).Mutate(ctx, m) + } + return next.Mutate(ctx, m) + }) + } +} + +// On executes the given hook only for the given operation. +// +// hook.On(Log, ent.Delete|ent.Create) +func On(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, HasOp(op)) +} + +// Unless skips the given hook only for the given operation. +// +// hook.Unless(Log, ent.Update|ent.UpdateOne) +func Unless(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, Not(HasOp(op))) +} + +// FixedError is a hook returning a fixed error. +func FixedError(err error) ent.Hook { + return func(ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { + return nil, err + }) + } +} + +// Reject returns a hook that rejects all operations that match op. +// +// func (T) Hooks() []ent.Hook { +// return []ent.Hook{ +// Reject(ent.Delete|ent.Update), +// } +// } +func Reject(op ent.Op) ent.Hook { + hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) + return On(hk, op) +} + +// Chain acts as a list of hooks and is effectively immutable. +// Once created, it will always hold the same set of hooks in the same order. +type Chain struct { + hooks []ent.Hook +} + +// NewChain creates a new chain of hooks. +func NewChain(hooks ...ent.Hook) Chain { + return Chain{append([]ent.Hook(nil), hooks...)} +} + +// Hook chains the list of hooks and returns the final hook. +func (c Chain) Hook() ent.Hook { + return func(mutator ent.Mutator) ent.Mutator { + for i := len(c.hooks) - 1; i >= 0; i-- { + mutator = c.hooks[i](mutator) + } + return mutator + } +} + +// Append extends a chain, adding the specified hook +// as the last ones in the mutation flow. +func (c Chain) Append(hooks ...ent.Hook) Chain { + newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) + newHooks = append(newHooks, c.hooks...) + newHooks = append(newHooks, hooks...) + return Chain{newHooks} +} + +// Extend extends a chain, adding the specified chain +// as the last ones in the mutation flow. +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.hooks...) +} diff --git a/db/ent/migrate/migrate.go b/db/ent/migrate/migrate.go new file mode 100644 index 0000000..1956a6b --- /dev/null +++ b/db/ent/migrate/migrate.go @@ -0,0 +1,64 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "context" + "fmt" + "io" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql/schema" +) + +var ( + // WithGlobalUniqueID sets the universal ids options to the migration. + // If this option is enabled, ent migration will allocate a 1<<32 range + // for the ids of each entity (table). + // Note that this option cannot be applied on tables that already exist. + WithGlobalUniqueID = schema.WithGlobalUniqueID + // WithDropColumn sets the drop column option to the migration. + // If this option is enabled, ent migration will drop old columns + // that were used for both fields and edges. This defaults to false. + WithDropColumn = schema.WithDropColumn + // WithDropIndex sets the drop index option to the migration. + // If this option is enabled, ent migration will drop old indexes + // that were defined in the schema. This defaults to false. + // Note that unique constraints are defined using `UNIQUE INDEX`, + // and therefore, it's recommended to enable this option to get more + // flexibility in the schema changes. + WithDropIndex = schema.WithDropIndex + // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. + WithForeignKeys = schema.WithForeignKeys +) + +// Schema is the API for creating, migrating and dropping a schema. +type Schema struct { + drv dialect.Driver +} + +// NewSchema creates a new schema client. +func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } + +// Create creates all schema resources. +func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { + return Create(ctx, s, Tables, opts...) +} + +// Create creates all table resources using the given schema driver. +func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.Create(ctx, tables...) +} + +// WriteTo writes the schema changes to w instead of running them against the database. +// +// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { +// log.Fatal(err) +// } +func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { + return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) +} diff --git a/db/ent/migrate/schema.go b/db/ent/migrate/schema.go new file mode 100644 index 0000000..032788f --- /dev/null +++ b/db/ent/migrate/schema.go @@ -0,0 +1,192 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/schema/field" +) + +var ( + // TexturesColumns holds the columns for the "textures" table. + TexturesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "texture_hash", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(100)"}}, + {Name: "texture_created_user", Type: field.TypeInt}, + } + // TexturesTable holds the schema information for the "textures" table. + TexturesTable = &schema.Table{ + Name: "textures", + Columns: TexturesColumns, + PrimaryKey: []*schema.Column{TexturesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "textures_users_created_user", + Columns: []*schema.Column{TexturesColumns[2]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "texture_texture_hash", + Unique: true, + Columns: []*schema.Column{TexturesColumns[1]}, + }, + }, + } + // UsersColumns holds the columns for the "users" table. + UsersColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "email", Type: field.TypeString, Unique: true, SchemaType: map[string]string{"mysql": "VARCHAR(30)"}}, + {Name: "password", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(80)"}}, + {Name: "salt", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(50)"}}, + {Name: "reg_ip", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(32)"}}, + {Name: "state", Type: field.TypeInt}, + {Name: "reg_time", Type: field.TypeInt64}, + } + // UsersTable holds the schema information for the "users" table. + UsersTable = &schema.Table{ + Name: "users", + Columns: UsersColumns, + PrimaryKey: []*schema.Column{UsersColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "user_email", + Unique: true, + Columns: []*schema.Column{UsersColumns[1]}, + }, + { + Name: "user_reg_ip", + Unique: false, + Columns: []*schema.Column{UsersColumns[4]}, + }, + }, + } + // UserProfilesColumns holds the columns for the "user_profiles" table. + UserProfilesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "name", Type: field.TypeString, Unique: true, SchemaType: map[string]string{"mysql": "VARCHAR(20)"}}, + {Name: "uuid", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(32)"}}, + {Name: "user_profile", Type: field.TypeInt, Unique: true}, + } + // UserProfilesTable holds the schema information for the "user_profiles" table. + UserProfilesTable = &schema.Table{ + Name: "user_profiles", + Columns: UserProfilesColumns, + PrimaryKey: []*schema.Column{UserProfilesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_profiles_users_profile", + Columns: []*schema.Column{UserProfilesColumns[3]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "userprofile_user_profile", + Unique: false, + Columns: []*schema.Column{UserProfilesColumns[3]}, + }, + { + Name: "userprofile_name", + Unique: false, + Columns: []*schema.Column{UserProfilesColumns[1]}, + }, + { + Name: "userprofile_uuid", + Unique: false, + Columns: []*schema.Column{UserProfilesColumns[2]}, + }, + }, + } + // UserTexturesColumns holds the columns for the "user_textures" table. + UserTexturesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "type", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(10)"}}, + {Name: "variant", Type: field.TypeString, SchemaType: map[string]string{"mysql": "VARCHAR(10)"}}, + {Name: "user_profile_id", Type: field.TypeInt}, + {Name: "texture_id", Type: field.TypeInt}, + } + // UserTexturesTable holds the schema information for the "user_textures" table. + UserTexturesTable = &schema.Table{ + Name: "user_textures", + Columns: UserTexturesColumns, + PrimaryKey: []*schema.Column{UserTexturesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_textures_user_profiles_user_profile", + Columns: []*schema.Column{UserTexturesColumns[3]}, + RefColumns: []*schema.Column{UserProfilesColumns[0]}, + OnDelete: schema.NoAction, + }, + { + Symbol: "user_textures_textures_texture", + Columns: []*schema.Column{UserTexturesColumns[4]}, + RefColumns: []*schema.Column{TexturesColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "usertexture_user_profile_id", + Unique: false, + Columns: []*schema.Column{UserTexturesColumns[3]}, + }, + { + Name: "usertexture_texture_id", + Unique: false, + Columns: []*schema.Column{UserTexturesColumns[4]}, + }, + { + Name: "usertexture_texture_id_user_profile_id", + Unique: true, + Columns: []*schema.Column{UserTexturesColumns[4], UserTexturesColumns[3]}, + }, + }, + } + // UserTokensColumns holds the columns for the "user_tokens" table. + UserTokensColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "token_id", Type: field.TypeUint64}, + {Name: "user_token", Type: field.TypeInt, Unique: true, Nullable: true}, + } + // UserTokensTable holds the schema information for the "user_tokens" table. + UserTokensTable = &schema.Table{ + Name: "user_tokens", + Columns: UserTokensColumns, + PrimaryKey: []*schema.Column{UserTokensColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_tokens_users_token", + Columns: []*schema.Column{UserTokensColumns[2]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.SetNull, + }, + }, + Indexes: []*schema.Index{ + { + Name: "usertoken_user_token", + Unique: false, + Columns: []*schema.Column{UserTokensColumns[2]}, + }, + }, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + TexturesTable, + UsersTable, + UserProfilesTable, + UserTexturesTable, + UserTokensTable, + } +) + +func init() { + TexturesTable.ForeignKeys[0].RefTable = UsersTable + UserProfilesTable.ForeignKeys[0].RefTable = UsersTable + UserTexturesTable.ForeignKeys[0].RefTable = UserProfilesTable + UserTexturesTable.ForeignKeys[1].RefTable = TexturesTable + UserTokensTable.ForeignKeys[0].RefTable = UsersTable +} diff --git a/db/ent/mutation.go b/db/ent/mutation.go new file mode 100644 index 0000000..5ca47ec --- /dev/null +++ b/db/ent/mutation.go @@ -0,0 +1,3105 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertexture" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +const ( + // Operation types. + OpCreate = ent.OpCreate + OpDelete = ent.OpDelete + OpDeleteOne = ent.OpDeleteOne + OpUpdate = ent.OpUpdate + OpUpdateOne = ent.OpUpdateOne + + // Node types. + TypeTexture = "Texture" + TypeUser = "User" + TypeUserProfile = "UserProfile" + TypeUserTexture = "UserTexture" + TypeUserToken = "UserToken" +) + +// TextureMutation represents an operation that mutates the Texture nodes in the graph. +type TextureMutation struct { + config + op Op + typ string + id *int + texture_hash *string + clearedFields map[string]struct{} + created_user *int + clearedcreated_user bool + user_profile map[int]struct{} + removeduser_profile map[int]struct{} + cleareduser_profile bool + usertexture map[int]struct{} + removedusertexture map[int]struct{} + clearedusertexture bool + done bool + oldValue func(context.Context) (*Texture, error) + predicates []predicate.Texture +} + +var _ ent.Mutation = (*TextureMutation)(nil) + +// textureOption allows management of the mutation configuration using functional options. +type textureOption func(*TextureMutation) + +// newTextureMutation creates new mutation for the Texture entity. +func newTextureMutation(c config, op Op, opts ...textureOption) *TextureMutation { + m := &TextureMutation{ + config: c, + op: op, + typ: TypeTexture, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withTextureID sets the ID field of the mutation. +func withTextureID(id int) textureOption { + return func(m *TextureMutation) { + var ( + err error + once sync.Once + value *Texture + ) + m.oldValue = func(ctx context.Context) (*Texture, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Texture.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withTexture sets the old Texture of the mutation. +func withTexture(node *Texture) textureOption { + return func(m *TextureMutation) { + m.oldValue = func(context.Context) (*Texture, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m TextureMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m TextureMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *TextureMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *TextureMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Texture.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetTextureHash sets the "texture_hash" field. +func (m *TextureMutation) SetTextureHash(s string) { + m.texture_hash = &s +} + +// TextureHash returns the value of the "texture_hash" field in the mutation. +func (m *TextureMutation) TextureHash() (r string, exists bool) { + v := m.texture_hash + if v == nil { + return + } + return *v, true +} + +// OldTextureHash returns the old "texture_hash" field's value of the Texture entity. +// If the Texture object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *TextureMutation) OldTextureHash(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTextureHash is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTextureHash requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTextureHash: %w", err) + } + return oldValue.TextureHash, nil +} + +// ResetTextureHash resets all changes to the "texture_hash" field. +func (m *TextureMutation) ResetTextureHash() { + m.texture_hash = nil +} + +// SetCreatedUserID sets the "created_user" edge to the User entity by id. +func (m *TextureMutation) SetCreatedUserID(id int) { + m.created_user = &id +} + +// ClearCreatedUser clears the "created_user" edge to the User entity. +func (m *TextureMutation) ClearCreatedUser() { + m.clearedcreated_user = true +} + +// CreatedUserCleared reports if the "created_user" edge to the User entity was cleared. +func (m *TextureMutation) CreatedUserCleared() bool { + return m.clearedcreated_user +} + +// CreatedUserID returns the "created_user" edge ID in the mutation. +func (m *TextureMutation) CreatedUserID() (id int, exists bool) { + if m.created_user != nil { + return *m.created_user, true + } + return +} + +// CreatedUserIDs returns the "created_user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// CreatedUserID instead. It exists only for internal usage by the builders. +func (m *TextureMutation) CreatedUserIDs() (ids []int) { + if id := m.created_user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetCreatedUser resets all changes to the "created_user" edge. +func (m *TextureMutation) ResetCreatedUser() { + m.created_user = nil + m.clearedcreated_user = false +} + +// AddUserProfileIDs adds the "user_profile" edge to the UserProfile entity by ids. +func (m *TextureMutation) AddUserProfileIDs(ids ...int) { + if m.user_profile == nil { + m.user_profile = make(map[int]struct{}) + } + for i := range ids { + m.user_profile[ids[i]] = struct{}{} + } +} + +// ClearUserProfile clears the "user_profile" edge to the UserProfile entity. +func (m *TextureMutation) ClearUserProfile() { + m.cleareduser_profile = true +} + +// UserProfileCleared reports if the "user_profile" edge to the UserProfile entity was cleared. +func (m *TextureMutation) UserProfileCleared() bool { + return m.cleareduser_profile +} + +// RemoveUserProfileIDs removes the "user_profile" edge to the UserProfile entity by IDs. +func (m *TextureMutation) RemoveUserProfileIDs(ids ...int) { + if m.removeduser_profile == nil { + m.removeduser_profile = make(map[int]struct{}) + } + for i := range ids { + delete(m.user_profile, ids[i]) + m.removeduser_profile[ids[i]] = struct{}{} + } +} + +// RemovedUserProfile returns the removed IDs of the "user_profile" edge to the UserProfile entity. +func (m *TextureMutation) RemovedUserProfileIDs() (ids []int) { + for id := range m.removeduser_profile { + ids = append(ids, id) + } + return +} + +// UserProfileIDs returns the "user_profile" edge IDs in the mutation. +func (m *TextureMutation) UserProfileIDs() (ids []int) { + for id := range m.user_profile { + ids = append(ids, id) + } + return +} + +// ResetUserProfile resets all changes to the "user_profile" edge. +func (m *TextureMutation) ResetUserProfile() { + m.user_profile = nil + m.cleareduser_profile = false + m.removeduser_profile = nil +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by ids. +func (m *TextureMutation) AddUsertextureIDs(ids ...int) { + if m.usertexture == nil { + m.usertexture = make(map[int]struct{}) + } + for i := range ids { + m.usertexture[ids[i]] = struct{}{} + } +} + +// ClearUsertexture clears the "usertexture" edge to the UserTexture entity. +func (m *TextureMutation) ClearUsertexture() { + m.clearedusertexture = true +} + +// UsertextureCleared reports if the "usertexture" edge to the UserTexture entity was cleared. +func (m *TextureMutation) UsertextureCleared() bool { + return m.clearedusertexture +} + +// RemoveUsertextureIDs removes the "usertexture" edge to the UserTexture entity by IDs. +func (m *TextureMutation) RemoveUsertextureIDs(ids ...int) { + if m.removedusertexture == nil { + m.removedusertexture = make(map[int]struct{}) + } + for i := range ids { + delete(m.usertexture, ids[i]) + m.removedusertexture[ids[i]] = struct{}{} + } +} + +// RemovedUsertexture returns the removed IDs of the "usertexture" edge to the UserTexture entity. +func (m *TextureMutation) RemovedUsertextureIDs() (ids []int) { + for id := range m.removedusertexture { + ids = append(ids, id) + } + return +} + +// UsertextureIDs returns the "usertexture" edge IDs in the mutation. +func (m *TextureMutation) UsertextureIDs() (ids []int) { + for id := range m.usertexture { + ids = append(ids, id) + } + return +} + +// ResetUsertexture resets all changes to the "usertexture" edge. +func (m *TextureMutation) ResetUsertexture() { + m.usertexture = nil + m.clearedusertexture = false + m.removedusertexture = nil +} + +// Where appends a list predicates to the TextureMutation builder. +func (m *TextureMutation) Where(ps ...predicate.Texture) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the TextureMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *TextureMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Texture, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *TextureMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *TextureMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Texture). +func (m *TextureMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *TextureMutation) Fields() []string { + fields := make([]string, 0, 1) + if m.texture_hash != nil { + fields = append(fields, texture.FieldTextureHash) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *TextureMutation) Field(name string) (ent.Value, bool) { + switch name { + case texture.FieldTextureHash: + return m.TextureHash() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *TextureMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case texture.FieldTextureHash: + return m.OldTextureHash(ctx) + } + return nil, fmt.Errorf("unknown Texture field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *TextureMutation) SetField(name string, value ent.Value) error { + switch name { + case texture.FieldTextureHash: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTextureHash(v) + return nil + } + return fmt.Errorf("unknown Texture field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *TextureMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *TextureMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *TextureMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown Texture numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *TextureMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *TextureMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *TextureMutation) ClearField(name string) error { + return fmt.Errorf("unknown Texture nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *TextureMutation) ResetField(name string) error { + switch name { + case texture.FieldTextureHash: + m.ResetTextureHash() + return nil + } + return fmt.Errorf("unknown Texture field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *TextureMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.created_user != nil { + edges = append(edges, texture.EdgeCreatedUser) + } + if m.user_profile != nil { + edges = append(edges, texture.EdgeUserProfile) + } + if m.usertexture != nil { + edges = append(edges, texture.EdgeUsertexture) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *TextureMutation) AddedIDs(name string) []ent.Value { + switch name { + case texture.EdgeCreatedUser: + if id := m.created_user; id != nil { + return []ent.Value{*id} + } + case texture.EdgeUserProfile: + ids := make([]ent.Value, 0, len(m.user_profile)) + for id := range m.user_profile { + ids = append(ids, id) + } + return ids + case texture.EdgeUsertexture: + ids := make([]ent.Value, 0, len(m.usertexture)) + for id := range m.usertexture { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *TextureMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + if m.removeduser_profile != nil { + edges = append(edges, texture.EdgeUserProfile) + } + if m.removedusertexture != nil { + edges = append(edges, texture.EdgeUsertexture) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *TextureMutation) RemovedIDs(name string) []ent.Value { + switch name { + case texture.EdgeUserProfile: + ids := make([]ent.Value, 0, len(m.removeduser_profile)) + for id := range m.removeduser_profile { + ids = append(ids, id) + } + return ids + case texture.EdgeUsertexture: + ids := make([]ent.Value, 0, len(m.removedusertexture)) + for id := range m.removedusertexture { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *TextureMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.clearedcreated_user { + edges = append(edges, texture.EdgeCreatedUser) + } + if m.cleareduser_profile { + edges = append(edges, texture.EdgeUserProfile) + } + if m.clearedusertexture { + edges = append(edges, texture.EdgeUsertexture) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *TextureMutation) EdgeCleared(name string) bool { + switch name { + case texture.EdgeCreatedUser: + return m.clearedcreated_user + case texture.EdgeUserProfile: + return m.cleareduser_profile + case texture.EdgeUsertexture: + return m.clearedusertexture + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *TextureMutation) ClearEdge(name string) error { + switch name { + case texture.EdgeCreatedUser: + m.ClearCreatedUser() + return nil + } + return fmt.Errorf("unknown Texture unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *TextureMutation) ResetEdge(name string) error { + switch name { + case texture.EdgeCreatedUser: + m.ResetCreatedUser() + return nil + case texture.EdgeUserProfile: + m.ResetUserProfile() + return nil + case texture.EdgeUsertexture: + m.ResetUsertexture() + return nil + } + return fmt.Errorf("unknown Texture edge %s", name) +} + +// UserMutation represents an operation that mutates the User nodes in the graph. +type UserMutation struct { + config + op Op + typ string + id *int + email *string + password *string + salt *string + reg_ip *string + state *int + addstate *int + reg_time *int64 + addreg_time *int64 + clearedFields map[string]struct{} + created_texture map[int]struct{} + removedcreated_texture map[int]struct{} + clearedcreated_texture bool + profile *int + clearedprofile bool + token *int + clearedtoken bool + done bool + oldValue func(context.Context) (*User, error) + predicates []predicate.User +} + +var _ ent.Mutation = (*UserMutation)(nil) + +// userOption allows management of the mutation configuration using functional options. +type userOption func(*UserMutation) + +// newUserMutation creates new mutation for the User entity. +func newUserMutation(c config, op Op, opts ...userOption) *UserMutation { + m := &UserMutation{ + config: c, + op: op, + typ: TypeUser, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserID sets the ID field of the mutation. +func withUserID(id int) userOption { + return func(m *UserMutation) { + var ( + err error + once sync.Once + value *User + ) + m.oldValue = func(ctx context.Context) (*User, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().User.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUser sets the old User of the mutation. +func withUser(node *User) userOption { + return func(m *UserMutation) { + m.oldValue = func(context.Context) (*User, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().User.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetEmail sets the "email" field. +func (m *UserMutation) SetEmail(s string) { + m.email = &s +} + +// Email returns the value of the "email" field in the mutation. +func (m *UserMutation) Email() (r string, exists bool) { + v := m.email + if v == nil { + return + } + return *v, true +} + +// OldEmail returns the old "email" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldEmail(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEmail is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEmail requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEmail: %w", err) + } + return oldValue.Email, nil +} + +// ResetEmail resets all changes to the "email" field. +func (m *UserMutation) ResetEmail() { + m.email = nil +} + +// SetPassword sets the "password" field. +func (m *UserMutation) SetPassword(s string) { + m.password = &s +} + +// Password returns the value of the "password" field in the mutation. +func (m *UserMutation) Password() (r string, exists bool) { + v := m.password + if v == nil { + return + } + return *v, true +} + +// OldPassword returns the old "password" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPassword is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPassword requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPassword: %w", err) + } + return oldValue.Password, nil +} + +// ResetPassword resets all changes to the "password" field. +func (m *UserMutation) ResetPassword() { + m.password = nil +} + +// SetSalt sets the "salt" field. +func (m *UserMutation) SetSalt(s string) { + m.salt = &s +} + +// Salt returns the value of the "salt" field in the mutation. +func (m *UserMutation) Salt() (r string, exists bool) { + v := m.salt + if v == nil { + return + } + return *v, true +} + +// OldSalt returns the old "salt" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldSalt(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSalt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSalt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSalt: %w", err) + } + return oldValue.Salt, nil +} + +// ResetSalt resets all changes to the "salt" field. +func (m *UserMutation) ResetSalt() { + m.salt = nil +} + +// SetRegIP sets the "reg_ip" field. +func (m *UserMutation) SetRegIP(s string) { + m.reg_ip = &s +} + +// RegIP returns the value of the "reg_ip" field in the mutation. +func (m *UserMutation) RegIP() (r string, exists bool) { + v := m.reg_ip + if v == nil { + return + } + return *v, true +} + +// OldRegIP returns the old "reg_ip" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldRegIP(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRegIP is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRegIP requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRegIP: %w", err) + } + return oldValue.RegIP, nil +} + +// ResetRegIP resets all changes to the "reg_ip" field. +func (m *UserMutation) ResetRegIP() { + m.reg_ip = nil +} + +// SetState sets the "state" field. +func (m *UserMutation) SetState(i int) { + m.state = &i + m.addstate = nil +} + +// State returns the value of the "state" field in the mutation. +func (m *UserMutation) State() (r int, exists bool) { + v := m.state + if v == nil { + return + } + return *v, true +} + +// OldState returns the old "state" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldState(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldState is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldState requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldState: %w", err) + } + return oldValue.State, nil +} + +// AddState adds i to the "state" field. +func (m *UserMutation) AddState(i int) { + if m.addstate != nil { + *m.addstate += i + } else { + m.addstate = &i + } +} + +// AddedState returns the value that was added to the "state" field in this mutation. +func (m *UserMutation) AddedState() (r int, exists bool) { + v := m.addstate + if v == nil { + return + } + return *v, true +} + +// ResetState resets all changes to the "state" field. +func (m *UserMutation) ResetState() { + m.state = nil + m.addstate = nil +} + +// SetRegTime sets the "reg_time" field. +func (m *UserMutation) SetRegTime(i int64) { + m.reg_time = &i + m.addreg_time = nil +} + +// RegTime returns the value of the "reg_time" field in the mutation. +func (m *UserMutation) RegTime() (r int64, exists bool) { + v := m.reg_time + if v == nil { + return + } + return *v, true +} + +// OldRegTime returns the old "reg_time" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldRegTime(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRegTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRegTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRegTime: %w", err) + } + return oldValue.RegTime, nil +} + +// AddRegTime adds i to the "reg_time" field. +func (m *UserMutation) AddRegTime(i int64) { + if m.addreg_time != nil { + *m.addreg_time += i + } else { + m.addreg_time = &i + } +} + +// AddedRegTime returns the value that was added to the "reg_time" field in this mutation. +func (m *UserMutation) AddedRegTime() (r int64, exists bool) { + v := m.addreg_time + if v == nil { + return + } + return *v, true +} + +// ResetRegTime resets all changes to the "reg_time" field. +func (m *UserMutation) ResetRegTime() { + m.reg_time = nil + m.addreg_time = nil +} + +// AddCreatedTextureIDs adds the "created_texture" edge to the Texture entity by ids. +func (m *UserMutation) AddCreatedTextureIDs(ids ...int) { + if m.created_texture == nil { + m.created_texture = make(map[int]struct{}) + } + for i := range ids { + m.created_texture[ids[i]] = struct{}{} + } +} + +// ClearCreatedTexture clears the "created_texture" edge to the Texture entity. +func (m *UserMutation) ClearCreatedTexture() { + m.clearedcreated_texture = true +} + +// CreatedTextureCleared reports if the "created_texture" edge to the Texture entity was cleared. +func (m *UserMutation) CreatedTextureCleared() bool { + return m.clearedcreated_texture +} + +// RemoveCreatedTextureIDs removes the "created_texture" edge to the Texture entity by IDs. +func (m *UserMutation) RemoveCreatedTextureIDs(ids ...int) { + if m.removedcreated_texture == nil { + m.removedcreated_texture = make(map[int]struct{}) + } + for i := range ids { + delete(m.created_texture, ids[i]) + m.removedcreated_texture[ids[i]] = struct{}{} + } +} + +// RemovedCreatedTexture returns the removed IDs of the "created_texture" edge to the Texture entity. +func (m *UserMutation) RemovedCreatedTextureIDs() (ids []int) { + for id := range m.removedcreated_texture { + ids = append(ids, id) + } + return +} + +// CreatedTextureIDs returns the "created_texture" edge IDs in the mutation. +func (m *UserMutation) CreatedTextureIDs() (ids []int) { + for id := range m.created_texture { + ids = append(ids, id) + } + return +} + +// ResetCreatedTexture resets all changes to the "created_texture" edge. +func (m *UserMutation) ResetCreatedTexture() { + m.created_texture = nil + m.clearedcreated_texture = false + m.removedcreated_texture = nil +} + +// SetProfileID sets the "profile" edge to the UserProfile entity by id. +func (m *UserMutation) SetProfileID(id int) { + m.profile = &id +} + +// ClearProfile clears the "profile" edge to the UserProfile entity. +func (m *UserMutation) ClearProfile() { + m.clearedprofile = true +} + +// ProfileCleared reports if the "profile" edge to the UserProfile entity was cleared. +func (m *UserMutation) ProfileCleared() bool { + return m.clearedprofile +} + +// ProfileID returns the "profile" edge ID in the mutation. +func (m *UserMutation) ProfileID() (id int, exists bool) { + if m.profile != nil { + return *m.profile, true + } + return +} + +// ProfileIDs returns the "profile" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ProfileID instead. It exists only for internal usage by the builders. +func (m *UserMutation) ProfileIDs() (ids []int) { + if id := m.profile; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetProfile resets all changes to the "profile" edge. +func (m *UserMutation) ResetProfile() { + m.profile = nil + m.clearedprofile = false +} + +// SetTokenID sets the "token" edge to the UserToken entity by id. +func (m *UserMutation) SetTokenID(id int) { + m.token = &id +} + +// ClearToken clears the "token" edge to the UserToken entity. +func (m *UserMutation) ClearToken() { + m.clearedtoken = true +} + +// TokenCleared reports if the "token" edge to the UserToken entity was cleared. +func (m *UserMutation) TokenCleared() bool { + return m.clearedtoken +} + +// TokenID returns the "token" edge ID in the mutation. +func (m *UserMutation) TokenID() (id int, exists bool) { + if m.token != nil { + return *m.token, true + } + return +} + +// TokenIDs returns the "token" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// TokenID instead. It exists only for internal usage by the builders. +func (m *UserMutation) TokenIDs() (ids []int) { + if id := m.token; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetToken resets all changes to the "token" edge. +func (m *UserMutation) ResetToken() { + m.token = nil + m.clearedtoken = false +} + +// Where appends a list predicates to the UserMutation builder. +func (m *UserMutation) Where(ps ...predicate.User) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.User, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (User). +func (m *UserMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserMutation) Fields() []string { + fields := make([]string, 0, 6) + if m.email != nil { + fields = append(fields, user.FieldEmail) + } + if m.password != nil { + fields = append(fields, user.FieldPassword) + } + if m.salt != nil { + fields = append(fields, user.FieldSalt) + } + if m.reg_ip != nil { + fields = append(fields, user.FieldRegIP) + } + if m.state != nil { + fields = append(fields, user.FieldState) + } + if m.reg_time != nil { + fields = append(fields, user.FieldRegTime) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserMutation) Field(name string) (ent.Value, bool) { + switch name { + case user.FieldEmail: + return m.Email() + case user.FieldPassword: + return m.Password() + case user.FieldSalt: + return m.Salt() + case user.FieldRegIP: + return m.RegIP() + case user.FieldState: + return m.State() + case user.FieldRegTime: + return m.RegTime() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case user.FieldEmail: + return m.OldEmail(ctx) + case user.FieldPassword: + return m.OldPassword(ctx) + case user.FieldSalt: + return m.OldSalt(ctx) + case user.FieldRegIP: + return m.OldRegIP(ctx) + case user.FieldState: + return m.OldState(ctx) + case user.FieldRegTime: + return m.OldRegTime(ctx) + } + return nil, fmt.Errorf("unknown User field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserMutation) SetField(name string, value ent.Value) error { + switch name { + case user.FieldEmail: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEmail(v) + return nil + case user.FieldPassword: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPassword(v) + return nil + case user.FieldSalt: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSalt(v) + return nil + case user.FieldRegIP: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRegIP(v) + return nil + case user.FieldState: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetState(v) + return nil + case user.FieldRegTime: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRegTime(v) + return nil + } + return fmt.Errorf("unknown User field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserMutation) AddedFields() []string { + var fields []string + if m.addstate != nil { + fields = append(fields, user.FieldState) + } + if m.addreg_time != nil { + fields = append(fields, user.FieldRegTime) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case user.FieldState: + return m.AddedState() + case user.FieldRegTime: + return m.AddedRegTime() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserMutation) AddField(name string, value ent.Value) error { + switch name { + case user.FieldState: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddState(v) + return nil + case user.FieldRegTime: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddRegTime(v) + return nil + } + return fmt.Errorf("unknown User numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserMutation) ClearField(name string) error { + return fmt.Errorf("unknown User nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserMutation) ResetField(name string) error { + switch name { + case user.FieldEmail: + m.ResetEmail() + return nil + case user.FieldPassword: + m.ResetPassword() + return nil + case user.FieldSalt: + m.ResetSalt() + return nil + case user.FieldRegIP: + m.ResetRegIP() + return nil + case user.FieldState: + m.ResetState() + return nil + case user.FieldRegTime: + m.ResetRegTime() + return nil + } + return fmt.Errorf("unknown User field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.created_texture != nil { + edges = append(edges, user.EdgeCreatedTexture) + } + if m.profile != nil { + edges = append(edges, user.EdgeProfile) + } + if m.token != nil { + edges = append(edges, user.EdgeToken) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserMutation) AddedIDs(name string) []ent.Value { + switch name { + case user.EdgeCreatedTexture: + ids := make([]ent.Value, 0, len(m.created_texture)) + for id := range m.created_texture { + ids = append(ids, id) + } + return ids + case user.EdgeProfile: + if id := m.profile; id != nil { + return []ent.Value{*id} + } + case user.EdgeToken: + if id := m.token; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + if m.removedcreated_texture != nil { + edges = append(edges, user.EdgeCreatedTexture) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserMutation) RemovedIDs(name string) []ent.Value { + switch name { + case user.EdgeCreatedTexture: + ids := make([]ent.Value, 0, len(m.removedcreated_texture)) + for id := range m.removedcreated_texture { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.clearedcreated_texture { + edges = append(edges, user.EdgeCreatedTexture) + } + if m.clearedprofile { + edges = append(edges, user.EdgeProfile) + } + if m.clearedtoken { + edges = append(edges, user.EdgeToken) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserMutation) EdgeCleared(name string) bool { + switch name { + case user.EdgeCreatedTexture: + return m.clearedcreated_texture + case user.EdgeProfile: + return m.clearedprofile + case user.EdgeToken: + return m.clearedtoken + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserMutation) ClearEdge(name string) error { + switch name { + case user.EdgeProfile: + m.ClearProfile() + return nil + case user.EdgeToken: + m.ClearToken() + return nil + } + return fmt.Errorf("unknown User unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserMutation) ResetEdge(name string) error { + switch name { + case user.EdgeCreatedTexture: + m.ResetCreatedTexture() + return nil + case user.EdgeProfile: + m.ResetProfile() + return nil + case user.EdgeToken: + m.ResetToken() + return nil + } + return fmt.Errorf("unknown User edge %s", name) +} + +// UserProfileMutation represents an operation that mutates the UserProfile nodes in the graph. +type UserProfileMutation struct { + config + op Op + typ string + id *int + name *string + uuid *string + clearedFields map[string]struct{} + user *int + cleareduser bool + texture map[int]struct{} + removedtexture map[int]struct{} + clearedtexture bool + usertexture map[int]struct{} + removedusertexture map[int]struct{} + clearedusertexture bool + done bool + oldValue func(context.Context) (*UserProfile, error) + predicates []predicate.UserProfile +} + +var _ ent.Mutation = (*UserProfileMutation)(nil) + +// userprofileOption allows management of the mutation configuration using functional options. +type userprofileOption func(*UserProfileMutation) + +// newUserProfileMutation creates new mutation for the UserProfile entity. +func newUserProfileMutation(c config, op Op, opts ...userprofileOption) *UserProfileMutation { + m := &UserProfileMutation{ + config: c, + op: op, + typ: TypeUserProfile, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserProfileID sets the ID field of the mutation. +func withUserProfileID(id int) userprofileOption { + return func(m *UserProfileMutation) { + var ( + err error + once sync.Once + value *UserProfile + ) + m.oldValue = func(ctx context.Context) (*UserProfile, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserProfile.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserProfile sets the old UserProfile of the mutation. +func withUserProfile(node *UserProfile) userprofileOption { + return func(m *UserProfileMutation) { + m.oldValue = func(context.Context) (*UserProfile, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserProfileMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserProfileMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserProfileMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserProfileMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserProfile.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetName sets the "name" field. +func (m *UserProfileMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *UserProfileMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the UserProfile entity. +// If the UserProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserProfileMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *UserProfileMutation) ResetName() { + m.name = nil +} + +// SetUUID sets the "uuid" field. +func (m *UserProfileMutation) SetUUID(s string) { + m.uuid = &s +} + +// UUID returns the value of the "uuid" field in the mutation. +func (m *UserProfileMutation) UUID() (r string, exists bool) { + v := m.uuid + if v == nil { + return + } + return *v, true +} + +// OldUUID returns the old "uuid" field's value of the UserProfile entity. +// If the UserProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserProfileMutation) OldUUID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUUID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUUID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUUID: %w", err) + } + return oldValue.UUID, nil +} + +// ResetUUID resets all changes to the "uuid" field. +func (m *UserProfileMutation) ResetUUID() { + m.uuid = nil +} + +// SetUserID sets the "user" edge to the User entity by id. +func (m *UserProfileMutation) SetUserID(id int) { + m.user = &id +} + +// ClearUser clears the "user" edge to the User entity. +func (m *UserProfileMutation) ClearUser() { + m.cleareduser = true +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *UserProfileMutation) UserCleared() bool { + return m.cleareduser +} + +// UserID returns the "user" edge ID in the mutation. +func (m *UserProfileMutation) UserID() (id int, exists bool) { + if m.user != nil { + return *m.user, true + } + return +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *UserProfileMutation) UserIDs() (ids []int) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *UserProfileMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// AddTextureIDs adds the "texture" edge to the Texture entity by ids. +func (m *UserProfileMutation) AddTextureIDs(ids ...int) { + if m.texture == nil { + m.texture = make(map[int]struct{}) + } + for i := range ids { + m.texture[ids[i]] = struct{}{} + } +} + +// ClearTexture clears the "texture" edge to the Texture entity. +func (m *UserProfileMutation) ClearTexture() { + m.clearedtexture = true +} + +// TextureCleared reports if the "texture" edge to the Texture entity was cleared. +func (m *UserProfileMutation) TextureCleared() bool { + return m.clearedtexture +} + +// RemoveTextureIDs removes the "texture" edge to the Texture entity by IDs. +func (m *UserProfileMutation) RemoveTextureIDs(ids ...int) { + if m.removedtexture == nil { + m.removedtexture = make(map[int]struct{}) + } + for i := range ids { + delete(m.texture, ids[i]) + m.removedtexture[ids[i]] = struct{}{} + } +} + +// RemovedTexture returns the removed IDs of the "texture" edge to the Texture entity. +func (m *UserProfileMutation) RemovedTextureIDs() (ids []int) { + for id := range m.removedtexture { + ids = append(ids, id) + } + return +} + +// TextureIDs returns the "texture" edge IDs in the mutation. +func (m *UserProfileMutation) TextureIDs() (ids []int) { + for id := range m.texture { + ids = append(ids, id) + } + return +} + +// ResetTexture resets all changes to the "texture" edge. +func (m *UserProfileMutation) ResetTexture() { + m.texture = nil + m.clearedtexture = false + m.removedtexture = nil +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by ids. +func (m *UserProfileMutation) AddUsertextureIDs(ids ...int) { + if m.usertexture == nil { + m.usertexture = make(map[int]struct{}) + } + for i := range ids { + m.usertexture[ids[i]] = struct{}{} + } +} + +// ClearUsertexture clears the "usertexture" edge to the UserTexture entity. +func (m *UserProfileMutation) ClearUsertexture() { + m.clearedusertexture = true +} + +// UsertextureCleared reports if the "usertexture" edge to the UserTexture entity was cleared. +func (m *UserProfileMutation) UsertextureCleared() bool { + return m.clearedusertexture +} + +// RemoveUsertextureIDs removes the "usertexture" edge to the UserTexture entity by IDs. +func (m *UserProfileMutation) RemoveUsertextureIDs(ids ...int) { + if m.removedusertexture == nil { + m.removedusertexture = make(map[int]struct{}) + } + for i := range ids { + delete(m.usertexture, ids[i]) + m.removedusertexture[ids[i]] = struct{}{} + } +} + +// RemovedUsertexture returns the removed IDs of the "usertexture" edge to the UserTexture entity. +func (m *UserProfileMutation) RemovedUsertextureIDs() (ids []int) { + for id := range m.removedusertexture { + ids = append(ids, id) + } + return +} + +// UsertextureIDs returns the "usertexture" edge IDs in the mutation. +func (m *UserProfileMutation) UsertextureIDs() (ids []int) { + for id := range m.usertexture { + ids = append(ids, id) + } + return +} + +// ResetUsertexture resets all changes to the "usertexture" edge. +func (m *UserProfileMutation) ResetUsertexture() { + m.usertexture = nil + m.clearedusertexture = false + m.removedusertexture = nil +} + +// Where appends a list predicates to the UserProfileMutation builder. +func (m *UserProfileMutation) Where(ps ...predicate.UserProfile) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserProfileMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserProfileMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserProfile, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserProfileMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserProfileMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserProfile). +func (m *UserProfileMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserProfileMutation) Fields() []string { + fields := make([]string, 0, 2) + if m.name != nil { + fields = append(fields, userprofile.FieldName) + } + if m.uuid != nil { + fields = append(fields, userprofile.FieldUUID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserProfileMutation) Field(name string) (ent.Value, bool) { + switch name { + case userprofile.FieldName: + return m.Name() + case userprofile.FieldUUID: + return m.UUID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserProfileMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case userprofile.FieldName: + return m.OldName(ctx) + case userprofile.FieldUUID: + return m.OldUUID(ctx) + } + return nil, fmt.Errorf("unknown UserProfile field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserProfileMutation) SetField(name string, value ent.Value) error { + switch name { + case userprofile.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case userprofile.FieldUUID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUUID(v) + return nil + } + return fmt.Errorf("unknown UserProfile field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserProfileMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserProfileMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserProfileMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown UserProfile numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserProfileMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserProfileMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserProfileMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserProfile nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserProfileMutation) ResetField(name string) error { + switch name { + case userprofile.FieldName: + m.ResetName() + return nil + case userprofile.FieldUUID: + m.ResetUUID() + return nil + } + return fmt.Errorf("unknown UserProfile field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserProfileMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.user != nil { + edges = append(edges, userprofile.EdgeUser) + } + if m.texture != nil { + edges = append(edges, userprofile.EdgeTexture) + } + if m.usertexture != nil { + edges = append(edges, userprofile.EdgeUsertexture) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserProfileMutation) AddedIDs(name string) []ent.Value { + switch name { + case userprofile.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + case userprofile.EdgeTexture: + ids := make([]ent.Value, 0, len(m.texture)) + for id := range m.texture { + ids = append(ids, id) + } + return ids + case userprofile.EdgeUsertexture: + ids := make([]ent.Value, 0, len(m.usertexture)) + for id := range m.usertexture { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserProfileMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + if m.removedtexture != nil { + edges = append(edges, userprofile.EdgeTexture) + } + if m.removedusertexture != nil { + edges = append(edges, userprofile.EdgeUsertexture) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserProfileMutation) RemovedIDs(name string) []ent.Value { + switch name { + case userprofile.EdgeTexture: + ids := make([]ent.Value, 0, len(m.removedtexture)) + for id := range m.removedtexture { + ids = append(ids, id) + } + return ids + case userprofile.EdgeUsertexture: + ids := make([]ent.Value, 0, len(m.removedusertexture)) + for id := range m.removedusertexture { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserProfileMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.cleareduser { + edges = append(edges, userprofile.EdgeUser) + } + if m.clearedtexture { + edges = append(edges, userprofile.EdgeTexture) + } + if m.clearedusertexture { + edges = append(edges, userprofile.EdgeUsertexture) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserProfileMutation) EdgeCleared(name string) bool { + switch name { + case userprofile.EdgeUser: + return m.cleareduser + case userprofile.EdgeTexture: + return m.clearedtexture + case userprofile.EdgeUsertexture: + return m.clearedusertexture + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserProfileMutation) ClearEdge(name string) error { + switch name { + case userprofile.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown UserProfile unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserProfileMutation) ResetEdge(name string) error { + switch name { + case userprofile.EdgeUser: + m.ResetUser() + return nil + case userprofile.EdgeTexture: + m.ResetTexture() + return nil + case userprofile.EdgeUsertexture: + m.ResetUsertexture() + return nil + } + return fmt.Errorf("unknown UserProfile edge %s", name) +} + +// UserTextureMutation represents an operation that mutates the UserTexture nodes in the graph. +type UserTextureMutation struct { + config + op Op + typ string + id *int + _type *string + variant *string + clearedFields map[string]struct{} + user_profile *int + cleareduser_profile bool + texture *int + clearedtexture bool + done bool + oldValue func(context.Context) (*UserTexture, error) + predicates []predicate.UserTexture +} + +var _ ent.Mutation = (*UserTextureMutation)(nil) + +// usertextureOption allows management of the mutation configuration using functional options. +type usertextureOption func(*UserTextureMutation) + +// newUserTextureMutation creates new mutation for the UserTexture entity. +func newUserTextureMutation(c config, op Op, opts ...usertextureOption) *UserTextureMutation { + m := &UserTextureMutation{ + config: c, + op: op, + typ: TypeUserTexture, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserTextureID sets the ID field of the mutation. +func withUserTextureID(id int) usertextureOption { + return func(m *UserTextureMutation) { + var ( + err error + once sync.Once + value *UserTexture + ) + m.oldValue = func(ctx context.Context) (*UserTexture, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserTexture.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserTexture sets the old UserTexture of the mutation. +func withUserTexture(node *UserTexture) usertextureOption { + return func(m *UserTextureMutation) { + m.oldValue = func(context.Context) (*UserTexture, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserTextureMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserTextureMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserTextureMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserTextureMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserTexture.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetUserProfileID sets the "user_profile_id" field. +func (m *UserTextureMutation) SetUserProfileID(i int) { + m.user_profile = &i +} + +// UserProfileID returns the value of the "user_profile_id" field in the mutation. +func (m *UserTextureMutation) UserProfileID() (r int, exists bool) { + v := m.user_profile + if v == nil { + return + } + return *v, true +} + +// OldUserProfileID returns the old "user_profile_id" field's value of the UserTexture entity. +// If the UserTexture object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserTextureMutation) OldUserProfileID(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserProfileID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserProfileID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserProfileID: %w", err) + } + return oldValue.UserProfileID, nil +} + +// ResetUserProfileID resets all changes to the "user_profile_id" field. +func (m *UserTextureMutation) ResetUserProfileID() { + m.user_profile = nil +} + +// SetTextureID sets the "texture_id" field. +func (m *UserTextureMutation) SetTextureID(i int) { + m.texture = &i +} + +// TextureID returns the value of the "texture_id" field in the mutation. +func (m *UserTextureMutation) TextureID() (r int, exists bool) { + v := m.texture + if v == nil { + return + } + return *v, true +} + +// OldTextureID returns the old "texture_id" field's value of the UserTexture entity. +// If the UserTexture object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserTextureMutation) OldTextureID(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTextureID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTextureID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTextureID: %w", err) + } + return oldValue.TextureID, nil +} + +// ResetTextureID resets all changes to the "texture_id" field. +func (m *UserTextureMutation) ResetTextureID() { + m.texture = nil +} + +// SetType sets the "type" field. +func (m *UserTextureMutation) SetType(s string) { + m._type = &s +} + +// GetType returns the value of the "type" field in the mutation. +func (m *UserTextureMutation) GetType() (r string, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the UserTexture entity. +// If the UserTexture object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserTextureMutation) OldType(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ResetType resets all changes to the "type" field. +func (m *UserTextureMutation) ResetType() { + m._type = nil +} + +// SetVariant sets the "variant" field. +func (m *UserTextureMutation) SetVariant(s string) { + m.variant = &s +} + +// Variant returns the value of the "variant" field in the mutation. +func (m *UserTextureMutation) Variant() (r string, exists bool) { + v := m.variant + if v == nil { + return + } + return *v, true +} + +// OldVariant returns the old "variant" field's value of the UserTexture entity. +// If the UserTexture object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserTextureMutation) OldVariant(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldVariant is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldVariant requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldVariant: %w", err) + } + return oldValue.Variant, nil +} + +// ResetVariant resets all changes to the "variant" field. +func (m *UserTextureMutation) ResetVariant() { + m.variant = nil +} + +// ClearUserProfile clears the "user_profile" edge to the UserProfile entity. +func (m *UserTextureMutation) ClearUserProfile() { + m.cleareduser_profile = true +} + +// UserProfileCleared reports if the "user_profile" edge to the UserProfile entity was cleared. +func (m *UserTextureMutation) UserProfileCleared() bool { + return m.cleareduser_profile +} + +// UserProfileIDs returns the "user_profile" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserProfileID instead. It exists only for internal usage by the builders. +func (m *UserTextureMutation) UserProfileIDs() (ids []int) { + if id := m.user_profile; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUserProfile resets all changes to the "user_profile" edge. +func (m *UserTextureMutation) ResetUserProfile() { + m.user_profile = nil + m.cleareduser_profile = false +} + +// ClearTexture clears the "texture" edge to the Texture entity. +func (m *UserTextureMutation) ClearTexture() { + m.clearedtexture = true +} + +// TextureCleared reports if the "texture" edge to the Texture entity was cleared. +func (m *UserTextureMutation) TextureCleared() bool { + return m.clearedtexture +} + +// TextureIDs returns the "texture" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// TextureID instead. It exists only for internal usage by the builders. +func (m *UserTextureMutation) TextureIDs() (ids []int) { + if id := m.texture; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetTexture resets all changes to the "texture" edge. +func (m *UserTextureMutation) ResetTexture() { + m.texture = nil + m.clearedtexture = false +} + +// Where appends a list predicates to the UserTextureMutation builder. +func (m *UserTextureMutation) Where(ps ...predicate.UserTexture) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserTextureMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserTextureMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserTexture, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserTextureMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserTextureMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserTexture). +func (m *UserTextureMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserTextureMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.user_profile != nil { + fields = append(fields, usertexture.FieldUserProfileID) + } + if m.texture != nil { + fields = append(fields, usertexture.FieldTextureID) + } + if m._type != nil { + fields = append(fields, usertexture.FieldType) + } + if m.variant != nil { + fields = append(fields, usertexture.FieldVariant) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserTextureMutation) Field(name string) (ent.Value, bool) { + switch name { + case usertexture.FieldUserProfileID: + return m.UserProfileID() + case usertexture.FieldTextureID: + return m.TextureID() + case usertexture.FieldType: + return m.GetType() + case usertexture.FieldVariant: + return m.Variant() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserTextureMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case usertexture.FieldUserProfileID: + return m.OldUserProfileID(ctx) + case usertexture.FieldTextureID: + return m.OldTextureID(ctx) + case usertexture.FieldType: + return m.OldType(ctx) + case usertexture.FieldVariant: + return m.OldVariant(ctx) + } + return nil, fmt.Errorf("unknown UserTexture field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserTextureMutation) SetField(name string, value ent.Value) error { + switch name { + case usertexture.FieldUserProfileID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserProfileID(v) + return nil + case usertexture.FieldTextureID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTextureID(v) + return nil + case usertexture.FieldType: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case usertexture.FieldVariant: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetVariant(v) + return nil + } + return fmt.Errorf("unknown UserTexture field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserTextureMutation) AddedFields() []string { + var fields []string + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserTextureMutation) AddedField(name string) (ent.Value, bool) { + switch name { + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserTextureMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown UserTexture numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserTextureMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserTextureMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserTextureMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserTexture nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserTextureMutation) ResetField(name string) error { + switch name { + case usertexture.FieldUserProfileID: + m.ResetUserProfileID() + return nil + case usertexture.FieldTextureID: + m.ResetTextureID() + return nil + case usertexture.FieldType: + m.ResetType() + return nil + case usertexture.FieldVariant: + m.ResetVariant() + return nil + } + return fmt.Errorf("unknown UserTexture field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserTextureMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.user_profile != nil { + edges = append(edges, usertexture.EdgeUserProfile) + } + if m.texture != nil { + edges = append(edges, usertexture.EdgeTexture) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserTextureMutation) AddedIDs(name string) []ent.Value { + switch name { + case usertexture.EdgeUserProfile: + if id := m.user_profile; id != nil { + return []ent.Value{*id} + } + case usertexture.EdgeTexture: + if id := m.texture; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserTextureMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserTextureMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserTextureMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.cleareduser_profile { + edges = append(edges, usertexture.EdgeUserProfile) + } + if m.clearedtexture { + edges = append(edges, usertexture.EdgeTexture) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserTextureMutation) EdgeCleared(name string) bool { + switch name { + case usertexture.EdgeUserProfile: + return m.cleareduser_profile + case usertexture.EdgeTexture: + return m.clearedtexture + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserTextureMutation) ClearEdge(name string) error { + switch name { + case usertexture.EdgeUserProfile: + m.ClearUserProfile() + return nil + case usertexture.EdgeTexture: + m.ClearTexture() + return nil + } + return fmt.Errorf("unknown UserTexture unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserTextureMutation) ResetEdge(name string) error { + switch name { + case usertexture.EdgeUserProfile: + m.ResetUserProfile() + return nil + case usertexture.EdgeTexture: + m.ResetTexture() + return nil + } + return fmt.Errorf("unknown UserTexture edge %s", name) +} + +// UserTokenMutation represents an operation that mutates the UserToken nodes in the graph. +type UserTokenMutation struct { + config + op Op + typ string + id *int + token_id *uint64 + addtoken_id *int64 + clearedFields map[string]struct{} + user *int + cleareduser bool + done bool + oldValue func(context.Context) (*UserToken, error) + predicates []predicate.UserToken +} + +var _ ent.Mutation = (*UserTokenMutation)(nil) + +// usertokenOption allows management of the mutation configuration using functional options. +type usertokenOption func(*UserTokenMutation) + +// newUserTokenMutation creates new mutation for the UserToken entity. +func newUserTokenMutation(c config, op Op, opts ...usertokenOption) *UserTokenMutation { + m := &UserTokenMutation{ + config: c, + op: op, + typ: TypeUserToken, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserTokenID sets the ID field of the mutation. +func withUserTokenID(id int) usertokenOption { + return func(m *UserTokenMutation) { + var ( + err error + once sync.Once + value *UserToken + ) + m.oldValue = func(ctx context.Context) (*UserToken, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserToken.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserToken sets the old UserToken of the mutation. +func withUserToken(node *UserToken) usertokenOption { + return func(m *UserTokenMutation) { + m.oldValue = func(context.Context) (*UserToken, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserTokenMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserTokenMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserTokenMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserTokenMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserToken.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetTokenID sets the "token_id" field. +func (m *UserTokenMutation) SetTokenID(u uint64) { + m.token_id = &u + m.addtoken_id = nil +} + +// TokenID returns the value of the "token_id" field in the mutation. +func (m *UserTokenMutation) TokenID() (r uint64, exists bool) { + v := m.token_id + if v == nil { + return + } + return *v, true +} + +// OldTokenID returns the old "token_id" field's value of the UserToken entity. +// If the UserToken object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserTokenMutation) OldTokenID(ctx context.Context) (v uint64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTokenID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTokenID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTokenID: %w", err) + } + return oldValue.TokenID, nil +} + +// AddTokenID adds u to the "token_id" field. +func (m *UserTokenMutation) AddTokenID(u int64) { + if m.addtoken_id != nil { + *m.addtoken_id += u + } else { + m.addtoken_id = &u + } +} + +// AddedTokenID returns the value that was added to the "token_id" field in this mutation. +func (m *UserTokenMutation) AddedTokenID() (r int64, exists bool) { + v := m.addtoken_id + if v == nil { + return + } + return *v, true +} + +// ResetTokenID resets all changes to the "token_id" field. +func (m *UserTokenMutation) ResetTokenID() { + m.token_id = nil + m.addtoken_id = nil +} + +// SetUserID sets the "user" edge to the User entity by id. +func (m *UserTokenMutation) SetUserID(id int) { + m.user = &id +} + +// ClearUser clears the "user" edge to the User entity. +func (m *UserTokenMutation) ClearUser() { + m.cleareduser = true +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *UserTokenMutation) UserCleared() bool { + return m.cleareduser +} + +// UserID returns the "user" edge ID in the mutation. +func (m *UserTokenMutation) UserID() (id int, exists bool) { + if m.user != nil { + return *m.user, true + } + return +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *UserTokenMutation) UserIDs() (ids []int) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *UserTokenMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the UserTokenMutation builder. +func (m *UserTokenMutation) Where(ps ...predicate.UserToken) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserTokenMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserTokenMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserToken, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserTokenMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserTokenMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserToken). +func (m *UserTokenMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserTokenMutation) Fields() []string { + fields := make([]string, 0, 1) + if m.token_id != nil { + fields = append(fields, usertoken.FieldTokenID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserTokenMutation) Field(name string) (ent.Value, bool) { + switch name { + case usertoken.FieldTokenID: + return m.TokenID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case usertoken.FieldTokenID: + return m.OldTokenID(ctx) + } + return nil, fmt.Errorf("unknown UserToken field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserTokenMutation) SetField(name string, value ent.Value) error { + switch name { + case usertoken.FieldTokenID: + v, ok := value.(uint64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTokenID(v) + return nil + } + return fmt.Errorf("unknown UserToken field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserTokenMutation) AddedFields() []string { + var fields []string + if m.addtoken_id != nil { + fields = append(fields, usertoken.FieldTokenID) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserTokenMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case usertoken.FieldTokenID: + return m.AddedTokenID() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserTokenMutation) AddField(name string, value ent.Value) error { + switch name { + case usertoken.FieldTokenID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddTokenID(v) + return nil + } + return fmt.Errorf("unknown UserToken numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserTokenMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserTokenMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserTokenMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserToken nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserTokenMutation) ResetField(name string) error { + switch name { + case usertoken.FieldTokenID: + m.ResetTokenID() + return nil + } + return fmt.Errorf("unknown UserToken field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserTokenMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, usertoken.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserTokenMutation) AddedIDs(name string) []ent.Value { + switch name { + case usertoken.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserTokenMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserTokenMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserTokenMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, usertoken.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserTokenMutation) EdgeCleared(name string) bool { + switch name { + case usertoken.EdgeUser: + return m.cleareduser + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserTokenMutation) ClearEdge(name string) error { + switch name { + case usertoken.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown UserToken unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserTokenMutation) ResetEdge(name string) error { + switch name { + case usertoken.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown UserToken edge %s", name) +} diff --git a/db/ent/predicate/predicate.go b/db/ent/predicate/predicate.go new file mode 100644 index 0000000..d18131d --- /dev/null +++ b/db/ent/predicate/predicate.go @@ -0,0 +1,22 @@ +// Code generated by ent, DO NOT EDIT. + +package predicate + +import ( + "entgo.io/ent/dialect/sql" +) + +// Texture is the predicate function for texture builders. +type Texture func(*sql.Selector) + +// User is the predicate function for user builders. +type User func(*sql.Selector) + +// UserProfile is the predicate function for userprofile builders. +type UserProfile func(*sql.Selector) + +// UserTexture is the predicate function for usertexture builders. +type UserTexture func(*sql.Selector) + +// UserToken is the predicate function for usertoken builders. +type UserToken func(*sql.Selector) diff --git a/db/ent/runtime.go b/db/ent/runtime.go new file mode 100644 index 0000000..793d053 --- /dev/null +++ b/db/ent/runtime.go @@ -0,0 +1,9 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +// The init function reads all schema descriptors with runtime code +// (default values, validators, hooks and policies) and stitches it +// to their package variables. +func init() { +} diff --git a/db/ent/runtime/runtime.go b/db/ent/runtime/runtime.go new file mode 100644 index 0000000..eff54c3 --- /dev/null +++ b/db/ent/runtime/runtime.go @@ -0,0 +1,9 @@ +// Code generated by ent, DO NOT EDIT. + +package runtime + +// The schema-stitching logic is generated in github.com/xmdhs/authlib-skin/db/ent/runtime.go + +const ( + Version = "(devel)" // Version of ent codegen. +) diff --git a/db/ent/schema/texture.go b/db/ent/schema/texture.go new file mode 100644 index 0000000..e6b5a01 --- /dev/null +++ b/db/ent/schema/texture.go @@ -0,0 +1,37 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// Texture holds the schema definition for the Texture entity. +type Texture struct { + ent.Schema +} + +// Fields of the Texture. +func (Texture) Fields() []ent.Field { + return []ent.Field{ + field.String("texture_hash").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(100)", + }), + } +} + +// Edges of the Texture. +func (Texture) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("created_user", User.Type).Unique().Required(), + edge.To("user_profile", UserProfile.Type).Through("usertexture", UserTexture.Type), + } +} + +func (Texture) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("texture_hash").Unique(), + } +} diff --git a/db/ent/schema/user.go b/db/ent/schema/user.go new file mode 100644 index 0000000..b7b304f --- /dev/null +++ b/db/ent/schema/user.go @@ -0,0 +1,52 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// User holds the schema definition for the User entity. +type User struct { + ent.Schema +} + +// Fields of the User. +func (User) Fields() []ent.Field { + return []ent.Field{ + field.String("email").Unique().SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(30)", + }), + field.String("password").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(80)", + }), + field.String("salt").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(50)", + }), + field.String("reg_ip").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(32)", + }), + // 二进制状态位 + // 第一位为 1 则是 admin + field.Int("state"), + field.Int64("reg_time"), + } +} + +// Edges of the User. +func (User) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("created_texture", Texture.Type).Ref("created_user"), + edge.To("profile", UserProfile.Type).Unique(), + edge.To("token", UserToken.Type).Unique(), + } +} + +func (User) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("email").Unique(), + index.Fields("reg_ip"), + } +} diff --git a/db/ent/schema/userprofile.go b/db/ent/schema/userprofile.go new file mode 100644 index 0000000..da9869d --- /dev/null +++ b/db/ent/schema/userprofile.go @@ -0,0 +1,42 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// UserProfile holds the schema definition for the UserProfile entity. +type UserProfile struct { + ent.Schema +} + +// Fields of the UserProfile. +func (UserProfile) Fields() []ent.Field { + return []ent.Field{ + field.String("name").Unique().SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(20)", + }), + field.String("uuid").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(32)", + }), + } +} + +// Edges of the UserProfile. +func (UserProfile) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("profile").Required().Unique(), + edge.From("texture", Texture.Type).Ref("user_profile").Through("usertexture", UserTexture.Type), + } +} + +func (UserProfile) Indexes() []ent.Index { + return []ent.Index{ + index.Edges("user"), + index.Fields("name"), + index.Fields("uuid"), + } +} diff --git a/db/ent/schema/usertexture.go b/db/ent/schema/usertexture.go new file mode 100644 index 0000000..4ee5c07 --- /dev/null +++ b/db/ent/schema/usertexture.go @@ -0,0 +1,51 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// UserTexture holds the schema definition for the UserTexture entity. +type UserTexture struct { + ent.Schema +} + +// Fields of the UserTexture. +func (UserTexture) Fields() []ent.Field { + return []ent.Field{ + field.Int("user_profile_id"), + field.Int("texture_id"), + // skin or cape + field.String("type").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(10)", + }), + // slim or "" + field.String("variant").SchemaType(map[string]string{ + dialect.MySQL: "VARCHAR(10)", + }), + } +} + +// Edges of the UserTexture. +func (UserTexture) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("user_profile", UserProfile.Type). + Unique(). + Required(). + Field("user_profile_id"), + edge.To("texture", Texture.Type). + Unique(). + Required(). + Field("texture_id"), + } +} + +func (UserTexture) Indexes() []ent.Index { + return []ent.Index{ + index.Edges("user_profile"), + index.Edges("texture"), + } +} diff --git a/db/ent/schema/usertoken.go b/db/ent/schema/usertoken.go new file mode 100644 index 0000000..637df5f --- /dev/null +++ b/db/ent/schema/usertoken.go @@ -0,0 +1,34 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// UserToken holds the schema definition for the UserToken entity. +type UserToken struct { + ent.Schema +} + +// Fields of the UserToken. +func (UserToken) Fields() []ent.Field { + return []ent.Field{ + // 用于验证 jwt token 是否被注销,若相同则有效 + field.Uint64("token_id"), + } +} + +// Edges of the UserToken. +func (UserToken) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type).Ref("token").Unique(), + } +} + +func (UserToken) Indexes() []ent.Index { + return []ent.Index{ + index.Edges("user"), + } +} diff --git a/db/ent/texture.go b/db/ent/texture.go new file mode 100644 index 0000000..e29d734 --- /dev/null +++ b/db/ent/texture.go @@ -0,0 +1,176 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/xmdhs/authlib-skin/db/ent/texture" + "github.com/xmdhs/authlib-skin/db/ent/user" +) + +// Texture is the model entity for the Texture schema. +type Texture struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // TextureHash holds the value of the "texture_hash" field. + TextureHash string `json:"texture_hash,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the TextureQuery when eager-loading is set. + Edges TextureEdges `json:"edges"` + texture_created_user *int + selectValues sql.SelectValues +} + +// TextureEdges holds the relations/edges for other nodes in the graph. +type TextureEdges struct { + // CreatedUser holds the value of the created_user edge. + CreatedUser *User `json:"created_user,omitempty"` + // UserProfile holds the value of the user_profile edge. + UserProfile []*UserProfile `json:"user_profile,omitempty"` + // Usertexture holds the value of the usertexture edge. + Usertexture []*UserTexture `json:"usertexture,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// CreatedUserOrErr returns the CreatedUser value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e TextureEdges) CreatedUserOrErr() (*User, error) { + if e.loadedTypes[0] { + if e.CreatedUser == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: user.Label} + } + return e.CreatedUser, nil + } + return nil, &NotLoadedError{edge: "created_user"} +} + +// UserProfileOrErr returns the UserProfile value or an error if the edge +// was not loaded in eager-loading. +func (e TextureEdges) UserProfileOrErr() ([]*UserProfile, error) { + if e.loadedTypes[1] { + return e.UserProfile, nil + } + return nil, &NotLoadedError{edge: "user_profile"} +} + +// UsertextureOrErr returns the Usertexture value or an error if the edge +// was not loaded in eager-loading. +func (e TextureEdges) UsertextureOrErr() ([]*UserTexture, error) { + if e.loadedTypes[2] { + return e.Usertexture, nil + } + return nil, &NotLoadedError{edge: "usertexture"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Texture) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case texture.FieldID: + values[i] = new(sql.NullInt64) + case texture.FieldTextureHash: + values[i] = new(sql.NullString) + case texture.ForeignKeys[0]: // texture_created_user + values[i] = new(sql.NullInt64) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Texture fields. +func (t *Texture) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case texture.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + t.ID = int(value.Int64) + case texture.FieldTextureHash: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field texture_hash", values[i]) + } else if value.Valid { + t.TextureHash = value.String + } + case texture.ForeignKeys[0]: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for edge-field texture_created_user", value) + } else if value.Valid { + t.texture_created_user = new(int) + *t.texture_created_user = int(value.Int64) + } + default: + t.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Texture. +// This includes values selected through modifiers, order, etc. +func (t *Texture) Value(name string) (ent.Value, error) { + return t.selectValues.Get(name) +} + +// QueryCreatedUser queries the "created_user" edge of the Texture entity. +func (t *Texture) QueryCreatedUser() *UserQuery { + return NewTextureClient(t.config).QueryCreatedUser(t) +} + +// QueryUserProfile queries the "user_profile" edge of the Texture entity. +func (t *Texture) QueryUserProfile() *UserProfileQuery { + return NewTextureClient(t.config).QueryUserProfile(t) +} + +// QueryUsertexture queries the "usertexture" edge of the Texture entity. +func (t *Texture) QueryUsertexture() *UserTextureQuery { + return NewTextureClient(t.config).QueryUsertexture(t) +} + +// Update returns a builder for updating this Texture. +// Note that you need to call Texture.Unwrap() before calling this method if this Texture +// was returned from a transaction, and the transaction was committed or rolled back. +func (t *Texture) Update() *TextureUpdateOne { + return NewTextureClient(t.config).UpdateOne(t) +} + +// Unwrap unwraps the Texture entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (t *Texture) Unwrap() *Texture { + _tx, ok := t.config.driver.(*txDriver) + if !ok { + panic("ent: Texture is not a transactional entity") + } + t.config.driver = _tx.drv + return t +} + +// String implements the fmt.Stringer. +func (t *Texture) String() string { + var builder strings.Builder + builder.WriteString("Texture(") + builder.WriteString(fmt.Sprintf("id=%v, ", t.ID)) + builder.WriteString("texture_hash=") + builder.WriteString(t.TextureHash) + builder.WriteByte(')') + return builder.String() +} + +// Textures is a parsable slice of Texture. +type Textures []*Texture diff --git a/db/ent/texture/texture.go b/db/ent/texture/texture.go new file mode 100644 index 0000000..2e4f0bf --- /dev/null +++ b/db/ent/texture/texture.go @@ -0,0 +1,146 @@ +// Code generated by ent, DO NOT EDIT. + +package texture + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the texture type in the database. + Label = "texture" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldTextureHash holds the string denoting the texture_hash field in the database. + FieldTextureHash = "texture_hash" + // EdgeCreatedUser holds the string denoting the created_user edge name in mutations. + EdgeCreatedUser = "created_user" + // EdgeUserProfile holds the string denoting the user_profile edge name in mutations. + EdgeUserProfile = "user_profile" + // EdgeUsertexture holds the string denoting the usertexture edge name in mutations. + EdgeUsertexture = "usertexture" + // Table holds the table name of the texture in the database. + Table = "textures" + // CreatedUserTable is the table that holds the created_user relation/edge. + CreatedUserTable = "textures" + // CreatedUserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + CreatedUserInverseTable = "users" + // CreatedUserColumn is the table column denoting the created_user relation/edge. + CreatedUserColumn = "texture_created_user" + // UserProfileTable is the table that holds the user_profile relation/edge. The primary key declared below. + UserProfileTable = "user_textures" + // UserProfileInverseTable is the table name for the UserProfile entity. + // It exists in this package in order to avoid circular dependency with the "userprofile" package. + UserProfileInverseTable = "user_profiles" + // UsertextureTable is the table that holds the usertexture relation/edge. + UsertextureTable = "user_textures" + // UsertextureInverseTable is the table name for the UserTexture entity. + // It exists in this package in order to avoid circular dependency with the "usertexture" package. + UsertextureInverseTable = "user_textures" + // UsertextureColumn is the table column denoting the usertexture relation/edge. + UsertextureColumn = "texture_id" +) + +// Columns holds all SQL columns for texture fields. +var Columns = []string{ + FieldID, + FieldTextureHash, +} + +// ForeignKeys holds the SQL foreign-keys that are owned by the "textures" +// table and are not defined as standalone fields in the schema. +var ForeignKeys = []string{ + "texture_created_user", +} + +var ( + // UserProfilePrimaryKey and UserProfileColumn2 are the table columns denoting the + // primary key for the user_profile relation (M2M). + UserProfilePrimaryKey = []string{"texture_id", "user_profile_id"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + for i := range ForeignKeys { + if column == ForeignKeys[i] { + return true + } + } + return false +} + +// OrderOption defines the ordering options for the Texture queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByTextureHash orders the results by the texture_hash field. +func ByTextureHash(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTextureHash, opts...).ToFunc() +} + +// ByCreatedUserField orders the results by created_user field. +func ByCreatedUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newCreatedUserStep(), sql.OrderByField(field, opts...)) + } +} + +// ByUserProfileCount orders the results by user_profile count. +func ByUserProfileCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newUserProfileStep(), opts...) + } +} + +// ByUserProfile orders the results by user_profile terms. +func ByUserProfile(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserProfileStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByUsertextureCount orders the results by usertexture count. +func ByUsertextureCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newUsertextureStep(), opts...) + } +} + +// ByUsertexture orders the results by usertexture terms. +func ByUsertexture(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUsertextureStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newCreatedUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(CreatedUserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, CreatedUserTable, CreatedUserColumn), + ) +} +func newUserProfileStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserProfileInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, UserProfileTable, UserProfilePrimaryKey...), + ) +} +func newUsertextureStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UsertextureInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, UsertextureTable, UsertextureColumn), + ) +} diff --git a/db/ent/texture/where.go b/db/ent/texture/where.go new file mode 100644 index 0000000..16eb75b --- /dev/null +++ b/db/ent/texture/where.go @@ -0,0 +1,225 @@ +// Code generated by ent, DO NOT EDIT. + +package texture + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/xmdhs/authlib-skin/db/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.Texture { + return predicate.Texture(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.Texture { + return predicate.Texture(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.Texture { + return predicate.Texture(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.Texture { + return predicate.Texture(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.Texture { + return predicate.Texture(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.Texture { + return predicate.Texture(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.Texture { + return predicate.Texture(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.Texture { + return predicate.Texture(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.Texture { + return predicate.Texture(sql.FieldLTE(FieldID, id)) +} + +// TextureHash applies equality check predicate on the "texture_hash" field. It's identical to TextureHashEQ. +func TextureHash(v string) predicate.Texture { + return predicate.Texture(sql.FieldEQ(FieldTextureHash, v)) +} + +// TextureHashEQ applies the EQ predicate on the "texture_hash" field. +func TextureHashEQ(v string) predicate.Texture { + return predicate.Texture(sql.FieldEQ(FieldTextureHash, v)) +} + +// TextureHashNEQ applies the NEQ predicate on the "texture_hash" field. +func TextureHashNEQ(v string) predicate.Texture { + return predicate.Texture(sql.FieldNEQ(FieldTextureHash, v)) +} + +// TextureHashIn applies the In predicate on the "texture_hash" field. +func TextureHashIn(vs ...string) predicate.Texture { + return predicate.Texture(sql.FieldIn(FieldTextureHash, vs...)) +} + +// TextureHashNotIn applies the NotIn predicate on the "texture_hash" field. +func TextureHashNotIn(vs ...string) predicate.Texture { + return predicate.Texture(sql.FieldNotIn(FieldTextureHash, vs...)) +} + +// TextureHashGT applies the GT predicate on the "texture_hash" field. +func TextureHashGT(v string) predicate.Texture { + return predicate.Texture(sql.FieldGT(FieldTextureHash, v)) +} + +// TextureHashGTE applies the GTE predicate on the "texture_hash" field. +func TextureHashGTE(v string) predicate.Texture { + return predicate.Texture(sql.FieldGTE(FieldTextureHash, v)) +} + +// TextureHashLT applies the LT predicate on the "texture_hash" field. +func TextureHashLT(v string) predicate.Texture { + return predicate.Texture(sql.FieldLT(FieldTextureHash, v)) +} + +// TextureHashLTE applies the LTE predicate on the "texture_hash" field. +func TextureHashLTE(v string) predicate.Texture { + return predicate.Texture(sql.FieldLTE(FieldTextureHash, v)) +} + +// TextureHashContains applies the Contains predicate on the "texture_hash" field. +func TextureHashContains(v string) predicate.Texture { + return predicate.Texture(sql.FieldContains(FieldTextureHash, v)) +} + +// TextureHashHasPrefix applies the HasPrefix predicate on the "texture_hash" field. +func TextureHashHasPrefix(v string) predicate.Texture { + return predicate.Texture(sql.FieldHasPrefix(FieldTextureHash, v)) +} + +// TextureHashHasSuffix applies the HasSuffix predicate on the "texture_hash" field. +func TextureHashHasSuffix(v string) predicate.Texture { + return predicate.Texture(sql.FieldHasSuffix(FieldTextureHash, v)) +} + +// TextureHashEqualFold applies the EqualFold predicate on the "texture_hash" field. +func TextureHashEqualFold(v string) predicate.Texture { + return predicate.Texture(sql.FieldEqualFold(FieldTextureHash, v)) +} + +// TextureHashContainsFold applies the ContainsFold predicate on the "texture_hash" field. +func TextureHashContainsFold(v string) predicate.Texture { + return predicate.Texture(sql.FieldContainsFold(FieldTextureHash, v)) +} + +// HasCreatedUser applies the HasEdge predicate on the "created_user" edge. +func HasCreatedUser() predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, CreatedUserTable, CreatedUserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasCreatedUserWith applies the HasEdge predicate on the "created_user" edge with a given conditions (other predicates). +func HasCreatedUserWith(preds ...predicate.User) predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + step := newCreatedUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasUserProfile applies the HasEdge predicate on the "user_profile" edge. +func HasUserProfile() predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, UserProfileTable, UserProfilePrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserProfileWith applies the HasEdge predicate on the "user_profile" edge with a given conditions (other predicates). +func HasUserProfileWith(preds ...predicate.UserProfile) predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + step := newUserProfileStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasUsertexture applies the HasEdge predicate on the "usertexture" edge. +func HasUsertexture() predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, UsertextureTable, UsertextureColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUsertextureWith applies the HasEdge predicate on the "usertexture" edge with a given conditions (other predicates). +func HasUsertextureWith(preds ...predicate.UserTexture) predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + step := newUsertextureStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Texture) predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Texture) predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Texture) predicate.Texture { + return predicate.Texture(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/db/ent/texture_create.go b/db/ent/texture_create.go new file mode 100644 index 0000000..3cd045c --- /dev/null +++ b/db/ent/texture_create.go @@ -0,0 +1,275 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "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/db/ent/usertexture" +) + +// TextureCreate is the builder for creating a Texture entity. +type TextureCreate struct { + config + mutation *TextureMutation + hooks []Hook +} + +// SetTextureHash sets the "texture_hash" field. +func (tc *TextureCreate) SetTextureHash(s string) *TextureCreate { + tc.mutation.SetTextureHash(s) + return tc +} + +// SetCreatedUserID sets the "created_user" edge to the User entity by ID. +func (tc *TextureCreate) SetCreatedUserID(id int) *TextureCreate { + tc.mutation.SetCreatedUserID(id) + return tc +} + +// SetCreatedUser sets the "created_user" edge to the User entity. +func (tc *TextureCreate) SetCreatedUser(u *User) *TextureCreate { + return tc.SetCreatedUserID(u.ID) +} + +// AddUserProfileIDs adds the "user_profile" edge to the UserProfile entity by IDs. +func (tc *TextureCreate) AddUserProfileIDs(ids ...int) *TextureCreate { + tc.mutation.AddUserProfileIDs(ids...) + return tc +} + +// AddUserProfile adds the "user_profile" edges to the UserProfile entity. +func (tc *TextureCreate) AddUserProfile(u ...*UserProfile) *TextureCreate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tc.AddUserProfileIDs(ids...) +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by IDs. +func (tc *TextureCreate) AddUsertextureIDs(ids ...int) *TextureCreate { + tc.mutation.AddUsertextureIDs(ids...) + return tc +} + +// AddUsertexture adds the "usertexture" edges to the UserTexture entity. +func (tc *TextureCreate) AddUsertexture(u ...*UserTexture) *TextureCreate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tc.AddUsertextureIDs(ids...) +} + +// Mutation returns the TextureMutation object of the builder. +func (tc *TextureCreate) Mutation() *TextureMutation { + return tc.mutation +} + +// Save creates the Texture in the database. +func (tc *TextureCreate) Save(ctx context.Context) (*Texture, error) { + return withHooks(ctx, tc.sqlSave, tc.mutation, tc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (tc *TextureCreate) SaveX(ctx context.Context) *Texture { + v, err := tc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (tc *TextureCreate) Exec(ctx context.Context) error { + _, err := tc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (tc *TextureCreate) ExecX(ctx context.Context) { + if err := tc.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (tc *TextureCreate) check() error { + if _, ok := tc.mutation.TextureHash(); !ok { + return &ValidationError{Name: "texture_hash", err: errors.New(`ent: missing required field "Texture.texture_hash"`)} + } + if _, ok := tc.mutation.CreatedUserID(); !ok { + return &ValidationError{Name: "created_user", err: errors.New(`ent: missing required edge "Texture.created_user"`)} + } + return nil +} + +func (tc *TextureCreate) sqlSave(ctx context.Context) (*Texture, error) { + if err := tc.check(); err != nil { + return nil, err + } + _node, _spec := tc.createSpec() + if err := sqlgraph.CreateNode(ctx, tc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + tc.mutation.id = &_node.ID + tc.mutation.done = true + return _node, nil +} + +func (tc *TextureCreate) createSpec() (*Texture, *sqlgraph.CreateSpec) { + var ( + _node = &Texture{config: tc.config} + _spec = sqlgraph.NewCreateSpec(texture.Table, sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt)) + ) + if value, ok := tc.mutation.TextureHash(); ok { + _spec.SetField(texture.FieldTextureHash, field.TypeString, value) + _node.TextureHash = value + } + if nodes := tc.mutation.CreatedUserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: texture.CreatedUserTable, + Columns: []string{texture.CreatedUserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.texture_created_user = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := tc.mutation.UserProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := tc.mutation.UsertextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// TextureCreateBulk is the builder for creating many Texture entities in bulk. +type TextureCreateBulk struct { + config + builders []*TextureCreate +} + +// Save creates the Texture entities in the database. +func (tcb *TextureCreateBulk) Save(ctx context.Context) ([]*Texture, error) { + specs := make([]*sqlgraph.CreateSpec, len(tcb.builders)) + nodes := make([]*Texture, len(tcb.builders)) + mutators := make([]Mutator, len(tcb.builders)) + for i := range tcb.builders { + func(i int, root context.Context) { + builder := tcb.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*TextureMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, tcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, tcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, tcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (tcb *TextureCreateBulk) SaveX(ctx context.Context) []*Texture { + v, err := tcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (tcb *TextureCreateBulk) Exec(ctx context.Context) error { + _, err := tcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (tcb *TextureCreateBulk) ExecX(ctx context.Context) { + if err := tcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/texture_delete.go b/db/ent/texture_delete.go new file mode 100644 index 0000000..fcdf8c7 --- /dev/null +++ b/db/ent/texture_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/texture" +) + +// TextureDelete is the builder for deleting a Texture entity. +type TextureDelete struct { + config + hooks []Hook + mutation *TextureMutation +} + +// Where appends a list predicates to the TextureDelete builder. +func (td *TextureDelete) Where(ps ...predicate.Texture) *TextureDelete { + td.mutation.Where(ps...) + return td +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (td *TextureDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, td.sqlExec, td.mutation, td.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (td *TextureDelete) ExecX(ctx context.Context) int { + n, err := td.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (td *TextureDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(texture.Table, sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt)) + if ps := td.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, td.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + td.mutation.done = true + return affected, err +} + +// TextureDeleteOne is the builder for deleting a single Texture entity. +type TextureDeleteOne struct { + td *TextureDelete +} + +// Where appends a list predicates to the TextureDelete builder. +func (tdo *TextureDeleteOne) Where(ps ...predicate.Texture) *TextureDeleteOne { + tdo.td.mutation.Where(ps...) + return tdo +} + +// Exec executes the deletion query. +func (tdo *TextureDeleteOne) Exec(ctx context.Context) error { + n, err := tdo.td.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{texture.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (tdo *TextureDeleteOne) ExecX(ctx context.Context) { + if err := tdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/texture_query.go b/db/ent/texture_query.go new file mode 100644 index 0000000..cf8b3eb --- /dev/null +++ b/db/ent/texture_query.go @@ -0,0 +1,850 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertexture" +) + +// TextureQuery is the builder for querying Texture entities. +type TextureQuery struct { + config + ctx *QueryContext + order []texture.OrderOption + inters []Interceptor + predicates []predicate.Texture + withCreatedUser *UserQuery + withUserProfile *UserProfileQuery + withUsertexture *UserTextureQuery + withFKs bool + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the TextureQuery builder. +func (tq *TextureQuery) Where(ps ...predicate.Texture) *TextureQuery { + tq.predicates = append(tq.predicates, ps...) + return tq +} + +// Limit the number of records to be returned by this query. +func (tq *TextureQuery) Limit(limit int) *TextureQuery { + tq.ctx.Limit = &limit + return tq +} + +// Offset to start from. +func (tq *TextureQuery) Offset(offset int) *TextureQuery { + tq.ctx.Offset = &offset + return tq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (tq *TextureQuery) Unique(unique bool) *TextureQuery { + tq.ctx.Unique = &unique + return tq +} + +// Order specifies how the records should be ordered. +func (tq *TextureQuery) Order(o ...texture.OrderOption) *TextureQuery { + tq.order = append(tq.order, o...) + return tq +} + +// QueryCreatedUser chains the current query on the "created_user" edge. +func (tq *TextureQuery) QueryCreatedUser() *UserQuery { + query := (&UserClient{config: tq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := tq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := tq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(texture.Table, texture.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, texture.CreatedUserTable, texture.CreatedUserColumn), + ) + fromU = sqlgraph.SetNeighbors(tq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryUserProfile chains the current query on the "user_profile" edge. +func (tq *TextureQuery) QueryUserProfile() *UserProfileQuery { + query := (&UserProfileClient{config: tq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := tq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := tq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(texture.Table, texture.FieldID, selector), + sqlgraph.To(userprofile.Table, userprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, texture.UserProfileTable, texture.UserProfilePrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(tq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryUsertexture chains the current query on the "usertexture" edge. +func (tq *TextureQuery) QueryUsertexture() *UserTextureQuery { + query := (&UserTextureClient{config: tq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := tq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := tq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(texture.Table, texture.FieldID, selector), + sqlgraph.To(usertexture.Table, usertexture.FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, texture.UsertextureTable, texture.UsertextureColumn), + ) + fromU = sqlgraph.SetNeighbors(tq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Texture entity from the query. +// Returns a *NotFoundError when no Texture was found. +func (tq *TextureQuery) First(ctx context.Context) (*Texture, error) { + nodes, err := tq.Limit(1).All(setContextOp(ctx, tq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{texture.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (tq *TextureQuery) FirstX(ctx context.Context) *Texture { + node, err := tq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Texture ID from the query. +// Returns a *NotFoundError when no Texture ID was found. +func (tq *TextureQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = tq.Limit(1).IDs(setContextOp(ctx, tq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{texture.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (tq *TextureQuery) FirstIDX(ctx context.Context) int { + id, err := tq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Texture entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Texture entity is found. +// Returns a *NotFoundError when no Texture entities are found. +func (tq *TextureQuery) Only(ctx context.Context) (*Texture, error) { + nodes, err := tq.Limit(2).All(setContextOp(ctx, tq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{texture.Label} + default: + return nil, &NotSingularError{texture.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (tq *TextureQuery) OnlyX(ctx context.Context) *Texture { + node, err := tq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Texture ID in the query. +// Returns a *NotSingularError when more than one Texture ID is found. +// Returns a *NotFoundError when no entities are found. +func (tq *TextureQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = tq.Limit(2).IDs(setContextOp(ctx, tq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{texture.Label} + default: + err = &NotSingularError{texture.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (tq *TextureQuery) OnlyIDX(ctx context.Context) int { + id, err := tq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Textures. +func (tq *TextureQuery) All(ctx context.Context) ([]*Texture, error) { + ctx = setContextOp(ctx, tq.ctx, "All") + if err := tq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Texture, *TextureQuery]() + return withInterceptors[[]*Texture](ctx, tq, qr, tq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (tq *TextureQuery) AllX(ctx context.Context) []*Texture { + nodes, err := tq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Texture IDs. +func (tq *TextureQuery) IDs(ctx context.Context) (ids []int, err error) { + if tq.ctx.Unique == nil && tq.path != nil { + tq.Unique(true) + } + ctx = setContextOp(ctx, tq.ctx, "IDs") + if err = tq.Select(texture.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (tq *TextureQuery) IDsX(ctx context.Context) []int { + ids, err := tq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (tq *TextureQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, tq.ctx, "Count") + if err := tq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, tq, querierCount[*TextureQuery](), tq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (tq *TextureQuery) CountX(ctx context.Context) int { + count, err := tq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (tq *TextureQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, tq.ctx, "Exist") + switch _, err := tq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (tq *TextureQuery) ExistX(ctx context.Context) bool { + exist, err := tq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the TextureQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (tq *TextureQuery) Clone() *TextureQuery { + if tq == nil { + return nil + } + return &TextureQuery{ + config: tq.config, + ctx: tq.ctx.Clone(), + order: append([]texture.OrderOption{}, tq.order...), + inters: append([]Interceptor{}, tq.inters...), + predicates: append([]predicate.Texture{}, tq.predicates...), + withCreatedUser: tq.withCreatedUser.Clone(), + withUserProfile: tq.withUserProfile.Clone(), + withUsertexture: tq.withUsertexture.Clone(), + // clone intermediate query. + sql: tq.sql.Clone(), + path: tq.path, + } +} + +// WithCreatedUser tells the query-builder to eager-load the nodes that are connected to +// the "created_user" edge. The optional arguments are used to configure the query builder of the edge. +func (tq *TextureQuery) WithCreatedUser(opts ...func(*UserQuery)) *TextureQuery { + query := (&UserClient{config: tq.config}).Query() + for _, opt := range opts { + opt(query) + } + tq.withCreatedUser = query + return tq +} + +// WithUserProfile tells the query-builder to eager-load the nodes that are connected to +// the "user_profile" edge. The optional arguments are used to configure the query builder of the edge. +func (tq *TextureQuery) WithUserProfile(opts ...func(*UserProfileQuery)) *TextureQuery { + query := (&UserProfileClient{config: tq.config}).Query() + for _, opt := range opts { + opt(query) + } + tq.withUserProfile = query + return tq +} + +// WithUsertexture tells the query-builder to eager-load the nodes that are connected to +// the "usertexture" edge. The optional arguments are used to configure the query builder of the edge. +func (tq *TextureQuery) WithUsertexture(opts ...func(*UserTextureQuery)) *TextureQuery { + query := (&UserTextureClient{config: tq.config}).Query() + for _, opt := range opts { + opt(query) + } + tq.withUsertexture = query + return tq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// TextureHash string `json:"texture_hash,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Texture.Query(). +// GroupBy(texture.FieldTextureHash). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (tq *TextureQuery) GroupBy(field string, fields ...string) *TextureGroupBy { + tq.ctx.Fields = append([]string{field}, fields...) + grbuild := &TextureGroupBy{build: tq} + grbuild.flds = &tq.ctx.Fields + grbuild.label = texture.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// TextureHash string `json:"texture_hash,omitempty"` +// } +// +// client.Texture.Query(). +// Select(texture.FieldTextureHash). +// Scan(ctx, &v) +func (tq *TextureQuery) Select(fields ...string) *TextureSelect { + tq.ctx.Fields = append(tq.ctx.Fields, fields...) + sbuild := &TextureSelect{TextureQuery: tq} + sbuild.label = texture.Label + sbuild.flds, sbuild.scan = &tq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a TextureSelect configured with the given aggregations. +func (tq *TextureQuery) Aggregate(fns ...AggregateFunc) *TextureSelect { + return tq.Select().Aggregate(fns...) +} + +func (tq *TextureQuery) prepareQuery(ctx context.Context) error { + for _, inter := range tq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, tq); err != nil { + return err + } + } + } + for _, f := range tq.ctx.Fields { + if !texture.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if tq.path != nil { + prev, err := tq.path(ctx) + if err != nil { + return err + } + tq.sql = prev + } + return nil +} + +func (tq *TextureQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Texture, error) { + var ( + nodes = []*Texture{} + withFKs = tq.withFKs + _spec = tq.querySpec() + loadedTypes = [3]bool{ + tq.withCreatedUser != nil, + tq.withUserProfile != nil, + tq.withUsertexture != nil, + } + ) + if tq.withCreatedUser != nil { + withFKs = true + } + if withFKs { + _spec.Node.Columns = append(_spec.Node.Columns, texture.ForeignKeys...) + } + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Texture).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Texture{config: tq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(tq.modifiers) > 0 { + _spec.Modifiers = tq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, tq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := tq.withCreatedUser; query != nil { + if err := tq.loadCreatedUser(ctx, query, nodes, nil, + func(n *Texture, e *User) { n.Edges.CreatedUser = e }); err != nil { + return nil, err + } + } + if query := tq.withUserProfile; query != nil { + if err := tq.loadUserProfile(ctx, query, nodes, + func(n *Texture) { n.Edges.UserProfile = []*UserProfile{} }, + func(n *Texture, e *UserProfile) { n.Edges.UserProfile = append(n.Edges.UserProfile, e) }); err != nil { + return nil, err + } + } + if query := tq.withUsertexture; query != nil { + if err := tq.loadUsertexture(ctx, query, nodes, + func(n *Texture) { n.Edges.Usertexture = []*UserTexture{} }, + func(n *Texture, e *UserTexture) { n.Edges.Usertexture = append(n.Edges.Usertexture, e) }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (tq *TextureQuery) loadCreatedUser(ctx context.Context, query *UserQuery, nodes []*Texture, init func(*Texture), assign func(*Texture, *User)) error { + ids := make([]int, 0, len(nodes)) + nodeids := make(map[int][]*Texture) + for i := range nodes { + if nodes[i].texture_created_user == nil { + continue + } + fk := *nodes[i].texture_created_user + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "texture_created_user" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (tq *TextureQuery) loadUserProfile(ctx context.Context, query *UserProfileQuery, nodes []*Texture, init func(*Texture), assign func(*Texture, *UserProfile)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[int]*Texture) + nids := make(map[int]map[*Texture]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(texture.UserProfileTable) + s.Join(joinT).On(s.C(userprofile.FieldID), joinT.C(texture.UserProfilePrimaryKey[1])) + s.Where(sql.InValues(joinT.C(texture.UserProfilePrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(texture.UserProfilePrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullInt64)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := int(values[0].(*sql.NullInt64).Int64) + inValue := int(values[1].(*sql.NullInt64).Int64) + if nids[inValue] == nil { + nids[inValue] = map[*Texture]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*UserProfile](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "user_profile" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (tq *TextureQuery) loadUsertexture(ctx context.Context, query *UserTextureQuery, nodes []*Texture, init func(*Texture), assign func(*Texture, *UserTexture)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int]*Texture) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(usertexture.FieldTextureID) + } + query.Where(predicate.UserTexture(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(texture.UsertextureColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.TextureID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "texture_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (tq *TextureQuery) sqlCount(ctx context.Context) (int, error) { + _spec := tq.querySpec() + if len(tq.modifiers) > 0 { + _spec.Modifiers = tq.modifiers + } + _spec.Node.Columns = tq.ctx.Fields + if len(tq.ctx.Fields) > 0 { + _spec.Unique = tq.ctx.Unique != nil && *tq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, tq.driver, _spec) +} + +func (tq *TextureQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(texture.Table, texture.Columns, sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt)) + _spec.From = tq.sql + if unique := tq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if tq.path != nil { + _spec.Unique = true + } + if fields := tq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, texture.FieldID) + for i := range fields { + if fields[i] != texture.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := tq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := tq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := tq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := tq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (tq *TextureQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(tq.driver.Dialect()) + t1 := builder.Table(texture.Table) + columns := tq.ctx.Fields + if len(columns) == 0 { + columns = texture.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if tq.sql != nil { + selector = tq.sql + selector.Select(selector.Columns(columns...)...) + } + if tq.ctx.Unique != nil && *tq.ctx.Unique { + selector.Distinct() + } + for _, m := range tq.modifiers { + m(selector) + } + for _, p := range tq.predicates { + p(selector) + } + for _, p := range tq.order { + p(selector) + } + if offset := tq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := tq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (tq *TextureQuery) ForUpdate(opts ...sql.LockOption) *TextureQuery { + if tq.driver.Dialect() == dialect.Postgres { + tq.Unique(false) + } + tq.modifiers = append(tq.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return tq +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (tq *TextureQuery) ForShare(opts ...sql.LockOption) *TextureQuery { + if tq.driver.Dialect() == dialect.Postgres { + tq.Unique(false) + } + tq.modifiers = append(tq.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return tq +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (tq *TextureQuery) ForUpdateA(opts ...sql.LockOption) *TextureQuery { + if tq.driver.Dialect() == dialect.SQLite { + return tq + } + return tq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (tq *TextureQuery) ForShareA(opts ...sql.LockOption) *TextureQuery { + if tq.driver.Dialect() == dialect.SQLite { + return tq + } + return tq.ForShare(opts...) +} + +// TextureGroupBy is the group-by builder for Texture entities. +type TextureGroupBy struct { + selector + build *TextureQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (tgb *TextureGroupBy) Aggregate(fns ...AggregateFunc) *TextureGroupBy { + tgb.fns = append(tgb.fns, fns...) + return tgb +} + +// Scan applies the selector query and scans the result into the given value. +func (tgb *TextureGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, tgb.build.ctx, "GroupBy") + if err := tgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*TextureQuery, *TextureGroupBy](ctx, tgb.build, tgb, tgb.build.inters, v) +} + +func (tgb *TextureGroupBy) sqlScan(ctx context.Context, root *TextureQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(tgb.fns)) + for _, fn := range tgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*tgb.flds)+len(tgb.fns)) + for _, f := range *tgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*tgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := tgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// TextureSelect is the builder for selecting fields of Texture entities. +type TextureSelect struct { + *TextureQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ts *TextureSelect) Aggregate(fns ...AggregateFunc) *TextureSelect { + ts.fns = append(ts.fns, fns...) + return ts +} + +// Scan applies the selector query and scans the result into the given value. +func (ts *TextureSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ts.ctx, "Select") + if err := ts.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*TextureQuery, *TextureSelect](ctx, ts.TextureQuery, ts, ts.inters, v) +} + +func (ts *TextureSelect) sqlScan(ctx context.Context, root *TextureQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ts.fns)) + for _, fn := range ts.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ts.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ts.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/db/ent/texture_update.go b/db/ent/texture_update.go new file mode 100644 index 0000000..359cf71 --- /dev/null +++ b/db/ent/texture_update.go @@ -0,0 +1,634 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertexture" +) + +// TextureUpdate is the builder for updating Texture entities. +type TextureUpdate struct { + config + hooks []Hook + mutation *TextureMutation +} + +// Where appends a list predicates to the TextureUpdate builder. +func (tu *TextureUpdate) Where(ps ...predicate.Texture) *TextureUpdate { + tu.mutation.Where(ps...) + return tu +} + +// SetTextureHash sets the "texture_hash" field. +func (tu *TextureUpdate) SetTextureHash(s string) *TextureUpdate { + tu.mutation.SetTextureHash(s) + return tu +} + +// SetCreatedUserID sets the "created_user" edge to the User entity by ID. +func (tu *TextureUpdate) SetCreatedUserID(id int) *TextureUpdate { + tu.mutation.SetCreatedUserID(id) + return tu +} + +// SetCreatedUser sets the "created_user" edge to the User entity. +func (tu *TextureUpdate) SetCreatedUser(u *User) *TextureUpdate { + return tu.SetCreatedUserID(u.ID) +} + +// AddUserProfileIDs adds the "user_profile" edge to the UserProfile entity by IDs. +func (tu *TextureUpdate) AddUserProfileIDs(ids ...int) *TextureUpdate { + tu.mutation.AddUserProfileIDs(ids...) + return tu +} + +// AddUserProfile adds the "user_profile" edges to the UserProfile entity. +func (tu *TextureUpdate) AddUserProfile(u ...*UserProfile) *TextureUpdate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tu.AddUserProfileIDs(ids...) +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by IDs. +func (tu *TextureUpdate) AddUsertextureIDs(ids ...int) *TextureUpdate { + tu.mutation.AddUsertextureIDs(ids...) + return tu +} + +// AddUsertexture adds the "usertexture" edges to the UserTexture entity. +func (tu *TextureUpdate) AddUsertexture(u ...*UserTexture) *TextureUpdate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tu.AddUsertextureIDs(ids...) +} + +// Mutation returns the TextureMutation object of the builder. +func (tu *TextureUpdate) Mutation() *TextureMutation { + return tu.mutation +} + +// ClearCreatedUser clears the "created_user" edge to the User entity. +func (tu *TextureUpdate) ClearCreatedUser() *TextureUpdate { + tu.mutation.ClearCreatedUser() + return tu +} + +// ClearUserProfile clears all "user_profile" edges to the UserProfile entity. +func (tu *TextureUpdate) ClearUserProfile() *TextureUpdate { + tu.mutation.ClearUserProfile() + return tu +} + +// RemoveUserProfileIDs removes the "user_profile" edge to UserProfile entities by IDs. +func (tu *TextureUpdate) RemoveUserProfileIDs(ids ...int) *TextureUpdate { + tu.mutation.RemoveUserProfileIDs(ids...) + return tu +} + +// RemoveUserProfile removes "user_profile" edges to UserProfile entities. +func (tu *TextureUpdate) RemoveUserProfile(u ...*UserProfile) *TextureUpdate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tu.RemoveUserProfileIDs(ids...) +} + +// ClearUsertexture clears all "usertexture" edges to the UserTexture entity. +func (tu *TextureUpdate) ClearUsertexture() *TextureUpdate { + tu.mutation.ClearUsertexture() + return tu +} + +// RemoveUsertextureIDs removes the "usertexture" edge to UserTexture entities by IDs. +func (tu *TextureUpdate) RemoveUsertextureIDs(ids ...int) *TextureUpdate { + tu.mutation.RemoveUsertextureIDs(ids...) + return tu +} + +// RemoveUsertexture removes "usertexture" edges to UserTexture entities. +func (tu *TextureUpdate) RemoveUsertexture(u ...*UserTexture) *TextureUpdate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tu.RemoveUsertextureIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (tu *TextureUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, tu.sqlSave, tu.mutation, tu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (tu *TextureUpdate) SaveX(ctx context.Context) int { + affected, err := tu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (tu *TextureUpdate) Exec(ctx context.Context) error { + _, err := tu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (tu *TextureUpdate) ExecX(ctx context.Context) { + if err := tu.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (tu *TextureUpdate) check() error { + if _, ok := tu.mutation.CreatedUserID(); tu.mutation.CreatedUserCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Texture.created_user"`) + } + return nil +} + +func (tu *TextureUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := tu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(texture.Table, texture.Columns, sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt)) + if ps := tu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := tu.mutation.TextureHash(); ok { + _spec.SetField(texture.FieldTextureHash, field.TypeString, value) + } + if tu.mutation.CreatedUserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: texture.CreatedUserTable, + Columns: []string{texture.CreatedUserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tu.mutation.CreatedUserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: texture.CreatedUserTable, + Columns: []string{texture.CreatedUserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if tu.mutation.UserProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tu.mutation.RemovedUserProfileIDs(); len(nodes) > 0 && !tu.mutation.UserProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tu.mutation.UserProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if tu.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tu.mutation.RemovedUsertextureIDs(); len(nodes) > 0 && !tu.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tu.mutation.UsertextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, tu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{texture.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + tu.mutation.done = true + return n, nil +} + +// TextureUpdateOne is the builder for updating a single Texture entity. +type TextureUpdateOne struct { + config + fields []string + hooks []Hook + mutation *TextureMutation +} + +// SetTextureHash sets the "texture_hash" field. +func (tuo *TextureUpdateOne) SetTextureHash(s string) *TextureUpdateOne { + tuo.mutation.SetTextureHash(s) + return tuo +} + +// SetCreatedUserID sets the "created_user" edge to the User entity by ID. +func (tuo *TextureUpdateOne) SetCreatedUserID(id int) *TextureUpdateOne { + tuo.mutation.SetCreatedUserID(id) + return tuo +} + +// SetCreatedUser sets the "created_user" edge to the User entity. +func (tuo *TextureUpdateOne) SetCreatedUser(u *User) *TextureUpdateOne { + return tuo.SetCreatedUserID(u.ID) +} + +// AddUserProfileIDs adds the "user_profile" edge to the UserProfile entity by IDs. +func (tuo *TextureUpdateOne) AddUserProfileIDs(ids ...int) *TextureUpdateOne { + tuo.mutation.AddUserProfileIDs(ids...) + return tuo +} + +// AddUserProfile adds the "user_profile" edges to the UserProfile entity. +func (tuo *TextureUpdateOne) AddUserProfile(u ...*UserProfile) *TextureUpdateOne { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tuo.AddUserProfileIDs(ids...) +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by IDs. +func (tuo *TextureUpdateOne) AddUsertextureIDs(ids ...int) *TextureUpdateOne { + tuo.mutation.AddUsertextureIDs(ids...) + return tuo +} + +// AddUsertexture adds the "usertexture" edges to the UserTexture entity. +func (tuo *TextureUpdateOne) AddUsertexture(u ...*UserTexture) *TextureUpdateOne { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tuo.AddUsertextureIDs(ids...) +} + +// Mutation returns the TextureMutation object of the builder. +func (tuo *TextureUpdateOne) Mutation() *TextureMutation { + return tuo.mutation +} + +// ClearCreatedUser clears the "created_user" edge to the User entity. +func (tuo *TextureUpdateOne) ClearCreatedUser() *TextureUpdateOne { + tuo.mutation.ClearCreatedUser() + return tuo +} + +// ClearUserProfile clears all "user_profile" edges to the UserProfile entity. +func (tuo *TextureUpdateOne) ClearUserProfile() *TextureUpdateOne { + tuo.mutation.ClearUserProfile() + return tuo +} + +// RemoveUserProfileIDs removes the "user_profile" edge to UserProfile entities by IDs. +func (tuo *TextureUpdateOne) RemoveUserProfileIDs(ids ...int) *TextureUpdateOne { + tuo.mutation.RemoveUserProfileIDs(ids...) + return tuo +} + +// RemoveUserProfile removes "user_profile" edges to UserProfile entities. +func (tuo *TextureUpdateOne) RemoveUserProfile(u ...*UserProfile) *TextureUpdateOne { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tuo.RemoveUserProfileIDs(ids...) +} + +// ClearUsertexture clears all "usertexture" edges to the UserTexture entity. +func (tuo *TextureUpdateOne) ClearUsertexture() *TextureUpdateOne { + tuo.mutation.ClearUsertexture() + return tuo +} + +// RemoveUsertextureIDs removes the "usertexture" edge to UserTexture entities by IDs. +func (tuo *TextureUpdateOne) RemoveUsertextureIDs(ids ...int) *TextureUpdateOne { + tuo.mutation.RemoveUsertextureIDs(ids...) + return tuo +} + +// RemoveUsertexture removes "usertexture" edges to UserTexture entities. +func (tuo *TextureUpdateOne) RemoveUsertexture(u ...*UserTexture) *TextureUpdateOne { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return tuo.RemoveUsertextureIDs(ids...) +} + +// Where appends a list predicates to the TextureUpdate builder. +func (tuo *TextureUpdateOne) Where(ps ...predicate.Texture) *TextureUpdateOne { + tuo.mutation.Where(ps...) + return tuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (tuo *TextureUpdateOne) Select(field string, fields ...string) *TextureUpdateOne { + tuo.fields = append([]string{field}, fields...) + return tuo +} + +// Save executes the query and returns the updated Texture entity. +func (tuo *TextureUpdateOne) Save(ctx context.Context) (*Texture, error) { + return withHooks(ctx, tuo.sqlSave, tuo.mutation, tuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (tuo *TextureUpdateOne) SaveX(ctx context.Context) *Texture { + node, err := tuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (tuo *TextureUpdateOne) Exec(ctx context.Context) error { + _, err := tuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (tuo *TextureUpdateOne) ExecX(ctx context.Context) { + if err := tuo.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (tuo *TextureUpdateOne) check() error { + if _, ok := tuo.mutation.CreatedUserID(); tuo.mutation.CreatedUserCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Texture.created_user"`) + } + return nil +} + +func (tuo *TextureUpdateOne) sqlSave(ctx context.Context) (_node *Texture, err error) { + if err := tuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(texture.Table, texture.Columns, sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt)) + id, ok := tuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Texture.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := tuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, texture.FieldID) + for _, f := range fields { + if !texture.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != texture.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := tuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := tuo.mutation.TextureHash(); ok { + _spec.SetField(texture.FieldTextureHash, field.TypeString, value) + } + if tuo.mutation.CreatedUserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: texture.CreatedUserTable, + Columns: []string{texture.CreatedUserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tuo.mutation.CreatedUserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: texture.CreatedUserTable, + Columns: []string{texture.CreatedUserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if tuo.mutation.UserProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tuo.mutation.RemovedUserProfileIDs(); len(nodes) > 0 && !tuo.mutation.UserProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tuo.mutation.UserProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: texture.UserProfileTable, + Columns: texture.UserProfilePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if tuo.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tuo.mutation.RemovedUsertextureIDs(); len(nodes) > 0 && !tuo.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := tuo.mutation.UsertextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: texture.UsertextureTable, + Columns: []string{texture.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &Texture{config: tuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, tuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{texture.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + tuo.mutation.done = true + return _node, nil +} diff --git a/db/ent/tx.go b/db/ent/tx.go new file mode 100644 index 0000000..11b4d49 --- /dev/null +++ b/db/ent/tx.go @@ -0,0 +1,222 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "sync" + + "entgo.io/ent/dialect" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // Texture is the client for interacting with the Texture builders. + Texture *TextureClient + // User is the client for interacting with the User builders. + User *UserClient + // UserProfile is the client for interacting with the UserProfile builders. + UserProfile *UserProfileClient + // UserTexture is the client for interacting with the UserTexture builders. + UserTexture *UserTextureClient + // UserToken is the client for interacting with the UserToken builders. + UserToken *UserTokenClient + + // lazily loaded. + client *Client + clientOnce sync.Once + // ctx lives for the life of the transaction. It is + // the same context used by the underlying connection. + ctx context.Context +} + +type ( + // Committer is the interface that wraps the Commit method. + Committer interface { + Commit(context.Context, *Tx) error + } + + // The CommitFunc type is an adapter to allow the use of ordinary + // function as a Committer. If f is a function with the appropriate + // signature, CommitFunc(f) is a Committer that calls f. + CommitFunc func(context.Context, *Tx) error + + // CommitHook defines the "commit middleware". A function that gets a Committer + // and returns a Committer. For example: + // + // hook := func(next ent.Committer) ent.Committer { + // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Commit(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + CommitHook func(Committer) Committer +) + +// Commit calls f(ctx, m). +func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Commit commits the transaction. +func (tx *Tx) Commit() error { + txDriver := tx.config.driver.(*txDriver) + var fn Committer = CommitFunc(func(context.Context, *Tx) error { + return txDriver.tx.Commit() + }) + txDriver.mu.Lock() + hooks := append([]CommitHook(nil), txDriver.onCommit...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Commit(tx.ctx, tx) +} + +// OnCommit adds a hook to call on commit. +func (tx *Tx) OnCommit(f CommitHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onCommit = append(txDriver.onCommit, f) + txDriver.mu.Unlock() +} + +type ( + // Rollbacker is the interface that wraps the Rollback method. + Rollbacker interface { + Rollback(context.Context, *Tx) error + } + + // The RollbackFunc type is an adapter to allow the use of ordinary + // function as a Rollbacker. If f is a function with the appropriate + // signature, RollbackFunc(f) is a Rollbacker that calls f. + RollbackFunc func(context.Context, *Tx) error + + // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker + // and returns a Rollbacker. For example: + // + // hook := func(next ent.Rollbacker) ent.Rollbacker { + // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Rollback(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + RollbackHook func(Rollbacker) Rollbacker +) + +// Rollback calls f(ctx, m). +func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Rollback rollbacks the transaction. +func (tx *Tx) Rollback() error { + txDriver := tx.config.driver.(*txDriver) + var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { + return txDriver.tx.Rollback() + }) + txDriver.mu.Lock() + hooks := append([]RollbackHook(nil), txDriver.onRollback...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Rollback(tx.ctx, tx) +} + +// OnRollback adds a hook to call on rollback. +func (tx *Tx) OnRollback(f RollbackHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onRollback = append(txDriver.onRollback, f) + txDriver.mu.Unlock() +} + +// Client returns a Client that binds to current transaction. +func (tx *Tx) Client() *Client { + tx.clientOnce.Do(func() { + tx.client = &Client{config: tx.config} + tx.client.init() + }) + return tx.client +} + +func (tx *Tx) init() { + tx.Texture = NewTextureClient(tx.config) + tx.User = NewUserClient(tx.config) + tx.UserProfile = NewUserProfileClient(tx.config) + tx.UserTexture = NewUserTextureClient(tx.config) + tx.UserToken = NewUserTokenClient(tx.config) +} + +// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. +// The idea is to support transactions without adding any extra code to the builders. +// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. +// Commit and Rollback are nop for the internal builders and the user must call one +// of them in order to commit or rollback the transaction. +// +// If a closed transaction is embedded in one of the generated entities, and the entity +// applies a query, for example: Texture.QueryXXX(), the query will be executed +// through the driver which created this transaction. +// +// Note that txDriver is not goroutine safe. +type txDriver struct { + // the driver we started the transaction from. + drv dialect.Driver + // tx is the underlying transaction. + tx dialect.Tx + // completion hooks. + mu sync.Mutex + onCommit []CommitHook + onRollback []RollbackHook +} + +// newTx creates a new transactional driver. +func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { + tx, err := drv.Tx(ctx) + if err != nil { + return nil, err + } + return &txDriver{tx: tx, drv: drv}, nil +} + +// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls +// from the internal builders. Should be called only by the internal builders. +func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } + +// Dialect returns the dialect of the driver we started the transaction from. +func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } + +// Close is a nop close. +func (*txDriver) Close() error { return nil } + +// Commit is a nop commit for the internal builders. +// User must call `Tx.Commit` in order to commit the transaction. +func (*txDriver) Commit() error { return nil } + +// Rollback is a nop rollback for the internal builders. +// User must call `Tx.Rollback` in order to rollback the transaction. +func (*txDriver) Rollback() error { return nil } + +// Exec calls tx.Exec. +func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { + return tx.tx.Exec(ctx, query, args, v) +} + +// Query calls tx.Query. +func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { + return tx.tx.Query(ctx, query, args, v) +} + +var _ dialect.Driver = (*txDriver)(nil) diff --git a/db/ent/user.go b/db/ent/user.go new file mode 100644 index 0000000..21ff390 --- /dev/null +++ b/db/ent/user.go @@ -0,0 +1,226 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// User is the model entity for the User schema. +type User struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // Email holds the value of the "email" field. + Email string `json:"email,omitempty"` + // Password holds the value of the "password" field. + Password string `json:"password,omitempty"` + // Salt holds the value of the "salt" field. + Salt string `json:"salt,omitempty"` + // RegIP holds the value of the "reg_ip" field. + RegIP string `json:"reg_ip,omitempty"` + // State holds the value of the "state" field. + State int `json:"state,omitempty"` + // RegTime holds the value of the "reg_time" field. + RegTime int64 `json:"reg_time,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserQuery when eager-loading is set. + Edges UserEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserEdges holds the relations/edges for other nodes in the graph. +type UserEdges struct { + // CreatedTexture holds the value of the created_texture edge. + CreatedTexture []*Texture `json:"created_texture,omitempty"` + // Profile holds the value of the profile edge. + Profile *UserProfile `json:"profile,omitempty"` + // Token holds the value of the token edge. + Token *UserToken `json:"token,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// CreatedTextureOrErr returns the CreatedTexture value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) CreatedTextureOrErr() ([]*Texture, error) { + if e.loadedTypes[0] { + return e.CreatedTexture, nil + } + return nil, &NotLoadedError{edge: "created_texture"} +} + +// ProfileOrErr returns the Profile value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserEdges) ProfileOrErr() (*UserProfile, error) { + if e.loadedTypes[1] { + if e.Profile == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: userprofile.Label} + } + return e.Profile, nil + } + return nil, &NotLoadedError{edge: "profile"} +} + +// TokenOrErr returns the Token value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserEdges) TokenOrErr() (*UserToken, error) { + if e.loadedTypes[2] { + if e.Token == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: usertoken.Label} + } + return e.Token, nil + } + return nil, &NotLoadedError{edge: "token"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*User) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case user.FieldID, user.FieldState, user.FieldRegTime: + values[i] = new(sql.NullInt64) + case user.FieldEmail, user.FieldPassword, user.FieldSalt, user.FieldRegIP: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the User fields. +func (u *User) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case user.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + u.ID = int(value.Int64) + case user.FieldEmail: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field email", values[i]) + } else if value.Valid { + u.Email = value.String + } + case user.FieldPassword: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field password", values[i]) + } else if value.Valid { + u.Password = value.String + } + case user.FieldSalt: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field salt", values[i]) + } else if value.Valid { + u.Salt = value.String + } + case user.FieldRegIP: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reg_ip", values[i]) + } else if value.Valid { + u.RegIP = value.String + } + case user.FieldState: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field state", values[i]) + } else if value.Valid { + u.State = int(value.Int64) + } + case user.FieldRegTime: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field reg_time", values[i]) + } else if value.Valid { + u.RegTime = value.Int64 + } + default: + u.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the User. +// This includes values selected through modifiers, order, etc. +func (u *User) Value(name string) (ent.Value, error) { + return u.selectValues.Get(name) +} + +// QueryCreatedTexture queries the "created_texture" edge of the User entity. +func (u *User) QueryCreatedTexture() *TextureQuery { + return NewUserClient(u.config).QueryCreatedTexture(u) +} + +// QueryProfile queries the "profile" edge of the User entity. +func (u *User) QueryProfile() *UserProfileQuery { + return NewUserClient(u.config).QueryProfile(u) +} + +// QueryToken queries the "token" edge of the User entity. +func (u *User) QueryToken() *UserTokenQuery { + return NewUserClient(u.config).QueryToken(u) +} + +// Update returns a builder for updating this User. +// Note that you need to call User.Unwrap() before calling this method if this User +// was returned from a transaction, and the transaction was committed or rolled back. +func (u *User) Update() *UserUpdateOne { + return NewUserClient(u.config).UpdateOne(u) +} + +// Unwrap unwraps the User entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (u *User) Unwrap() *User { + _tx, ok := u.config.driver.(*txDriver) + if !ok { + panic("ent: User is not a transactional entity") + } + u.config.driver = _tx.drv + return u +} + +// String implements the fmt.Stringer. +func (u *User) String() string { + var builder strings.Builder + builder.WriteString("User(") + builder.WriteString(fmt.Sprintf("id=%v, ", u.ID)) + builder.WriteString("email=") + builder.WriteString(u.Email) + builder.WriteString(", ") + builder.WriteString("password=") + builder.WriteString(u.Password) + builder.WriteString(", ") + builder.WriteString("salt=") + builder.WriteString(u.Salt) + builder.WriteString(", ") + builder.WriteString("reg_ip=") + builder.WriteString(u.RegIP) + builder.WriteString(", ") + builder.WriteString("state=") + builder.WriteString(fmt.Sprintf("%v", u.State)) + builder.WriteString(", ") + builder.WriteString("reg_time=") + builder.WriteString(fmt.Sprintf("%v", u.RegTime)) + builder.WriteByte(')') + return builder.String() +} + +// Users is a parsable slice of User. +type Users []*User diff --git a/db/ent/user/user.go b/db/ent/user/user.go new file mode 100644 index 0000000..0604af8 --- /dev/null +++ b/db/ent/user/user.go @@ -0,0 +1,164 @@ +// Code generated by ent, DO NOT EDIT. + +package user + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the user type in the database. + Label = "user" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldEmail holds the string denoting the email field in the database. + FieldEmail = "email" + // FieldPassword holds the string denoting the password field in the database. + FieldPassword = "password" + // FieldSalt holds the string denoting the salt field in the database. + FieldSalt = "salt" + // FieldRegIP holds the string denoting the reg_ip field in the database. + FieldRegIP = "reg_ip" + // FieldState holds the string denoting the state field in the database. + FieldState = "state" + // FieldRegTime holds the string denoting the reg_time field in the database. + FieldRegTime = "reg_time" + // EdgeCreatedTexture holds the string denoting the created_texture edge name in mutations. + EdgeCreatedTexture = "created_texture" + // EdgeProfile holds the string denoting the profile edge name in mutations. + EdgeProfile = "profile" + // EdgeToken holds the string denoting the token edge name in mutations. + EdgeToken = "token" + // Table holds the table name of the user in the database. + Table = "users" + // CreatedTextureTable is the table that holds the created_texture relation/edge. + CreatedTextureTable = "textures" + // CreatedTextureInverseTable is the table name for the Texture entity. + // It exists in this package in order to avoid circular dependency with the "texture" package. + CreatedTextureInverseTable = "textures" + // CreatedTextureColumn is the table column denoting the created_texture relation/edge. + CreatedTextureColumn = "texture_created_user" + // ProfileTable is the table that holds the profile relation/edge. + ProfileTable = "user_profiles" + // ProfileInverseTable is the table name for the UserProfile entity. + // It exists in this package in order to avoid circular dependency with the "userprofile" package. + ProfileInverseTable = "user_profiles" + // ProfileColumn is the table column denoting the profile relation/edge. + ProfileColumn = "user_profile" + // TokenTable is the table that holds the token relation/edge. + TokenTable = "user_tokens" + // TokenInverseTable is the table name for the UserToken entity. + // It exists in this package in order to avoid circular dependency with the "usertoken" package. + TokenInverseTable = "user_tokens" + // TokenColumn is the table column denoting the token relation/edge. + TokenColumn = "user_token" +) + +// Columns holds all SQL columns for user fields. +var Columns = []string{ + FieldID, + FieldEmail, + FieldPassword, + FieldSalt, + FieldRegIP, + FieldState, + FieldRegTime, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +// OrderOption defines the ordering options for the User queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByEmail orders the results by the email field. +func ByEmail(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldEmail, opts...).ToFunc() +} + +// ByPassword orders the results by the password field. +func ByPassword(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPassword, opts...).ToFunc() +} + +// BySalt orders the results by the salt field. +func BySalt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSalt, opts...).ToFunc() +} + +// ByRegIP orders the results by the reg_ip field. +func ByRegIP(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRegIP, opts...).ToFunc() +} + +// ByState orders the results by the state field. +func ByState(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldState, opts...).ToFunc() +} + +// ByRegTime orders the results by the reg_time field. +func ByRegTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRegTime, opts...).ToFunc() +} + +// ByCreatedTextureCount orders the results by created_texture count. +func ByCreatedTextureCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newCreatedTextureStep(), opts...) + } +} + +// ByCreatedTexture orders the results by created_texture terms. +func ByCreatedTexture(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newCreatedTextureStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByProfileField orders the results by profile field. +func ByProfileField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newProfileStep(), sql.OrderByField(field, opts...)) + } +} + +// ByTokenField orders the results by token field. +func ByTokenField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTokenStep(), sql.OrderByField(field, opts...)) + } +} +func newCreatedTextureStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(CreatedTextureInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, CreatedTextureTable, CreatedTextureColumn), + ) +} +func newProfileStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ProfileInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, ProfileTable, ProfileColumn), + ) +} +func newTokenStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TokenInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, TokenTable, TokenColumn), + ) +} diff --git a/db/ent/user/where.go b/db/ent/user/where.go new file mode 100644 index 0000000..f19d46e --- /dev/null +++ b/db/ent/user/where.go @@ -0,0 +1,525 @@ +// Code generated by ent, DO NOT EDIT. + +package user + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/xmdhs/authlib-skin/db/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.User { + return predicate.User(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.User { + return predicate.User(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.User { + return predicate.User(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.User { + return predicate.User(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.User { + return predicate.User(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.User { + return predicate.User(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.User { + return predicate.User(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.User { + return predicate.User(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.User { + return predicate.User(sql.FieldLTE(FieldID, id)) +} + +// Email applies equality check predicate on the "email" field. It's identical to EmailEQ. +func Email(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldEmail, v)) +} + +// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ. +func Password(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldPassword, v)) +} + +// Salt applies equality check predicate on the "salt" field. It's identical to SaltEQ. +func Salt(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldSalt, v)) +} + +// RegIP applies equality check predicate on the "reg_ip" field. It's identical to RegIPEQ. +func RegIP(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldRegIP, v)) +} + +// State applies equality check predicate on the "state" field. It's identical to StateEQ. +func State(v int) predicate.User { + return predicate.User(sql.FieldEQ(FieldState, v)) +} + +// RegTime applies equality check predicate on the "reg_time" field. It's identical to RegTimeEQ. +func RegTime(v int64) predicate.User { + return predicate.User(sql.FieldEQ(FieldRegTime, v)) +} + +// EmailEQ applies the EQ predicate on the "email" field. +func EmailEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldEmail, v)) +} + +// EmailNEQ applies the NEQ predicate on the "email" field. +func EmailNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldEmail, v)) +} + +// EmailIn applies the In predicate on the "email" field. +func EmailIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldEmail, vs...)) +} + +// EmailNotIn applies the NotIn predicate on the "email" field. +func EmailNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldEmail, vs...)) +} + +// EmailGT applies the GT predicate on the "email" field. +func EmailGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldEmail, v)) +} + +// EmailGTE applies the GTE predicate on the "email" field. +func EmailGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldEmail, v)) +} + +// EmailLT applies the LT predicate on the "email" field. +func EmailLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldEmail, v)) +} + +// EmailLTE applies the LTE predicate on the "email" field. +func EmailLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldEmail, v)) +} + +// EmailContains applies the Contains predicate on the "email" field. +func EmailContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldEmail, v)) +} + +// EmailHasPrefix applies the HasPrefix predicate on the "email" field. +func EmailHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldEmail, v)) +} + +// EmailHasSuffix applies the HasSuffix predicate on the "email" field. +func EmailHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldEmail, v)) +} + +// EmailEqualFold applies the EqualFold predicate on the "email" field. +func EmailEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldEmail, v)) +} + +// EmailContainsFold applies the ContainsFold predicate on the "email" field. +func EmailContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldEmail, v)) +} + +// PasswordEQ applies the EQ predicate on the "password" field. +func PasswordEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldPassword, v)) +} + +// PasswordNEQ applies the NEQ predicate on the "password" field. +func PasswordNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldPassword, v)) +} + +// PasswordIn applies the In predicate on the "password" field. +func PasswordIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldPassword, vs...)) +} + +// PasswordNotIn applies the NotIn predicate on the "password" field. +func PasswordNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldPassword, vs...)) +} + +// PasswordGT applies the GT predicate on the "password" field. +func PasswordGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldPassword, v)) +} + +// PasswordGTE applies the GTE predicate on the "password" field. +func PasswordGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldPassword, v)) +} + +// PasswordLT applies the LT predicate on the "password" field. +func PasswordLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldPassword, v)) +} + +// PasswordLTE applies the LTE predicate on the "password" field. +func PasswordLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldPassword, v)) +} + +// PasswordContains applies the Contains predicate on the "password" field. +func PasswordContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldPassword, v)) +} + +// PasswordHasPrefix applies the HasPrefix predicate on the "password" field. +func PasswordHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldPassword, v)) +} + +// PasswordHasSuffix applies the HasSuffix predicate on the "password" field. +func PasswordHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldPassword, v)) +} + +// PasswordEqualFold applies the EqualFold predicate on the "password" field. +func PasswordEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldPassword, v)) +} + +// PasswordContainsFold applies the ContainsFold predicate on the "password" field. +func PasswordContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldPassword, v)) +} + +// SaltEQ applies the EQ predicate on the "salt" field. +func SaltEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldSalt, v)) +} + +// SaltNEQ applies the NEQ predicate on the "salt" field. +func SaltNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldSalt, v)) +} + +// SaltIn applies the In predicate on the "salt" field. +func SaltIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldSalt, vs...)) +} + +// SaltNotIn applies the NotIn predicate on the "salt" field. +func SaltNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldSalt, vs...)) +} + +// SaltGT applies the GT predicate on the "salt" field. +func SaltGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldSalt, v)) +} + +// SaltGTE applies the GTE predicate on the "salt" field. +func SaltGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldSalt, v)) +} + +// SaltLT applies the LT predicate on the "salt" field. +func SaltLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldSalt, v)) +} + +// SaltLTE applies the LTE predicate on the "salt" field. +func SaltLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldSalt, v)) +} + +// SaltContains applies the Contains predicate on the "salt" field. +func SaltContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldSalt, v)) +} + +// SaltHasPrefix applies the HasPrefix predicate on the "salt" field. +func SaltHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldSalt, v)) +} + +// SaltHasSuffix applies the HasSuffix predicate on the "salt" field. +func SaltHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldSalt, v)) +} + +// SaltEqualFold applies the EqualFold predicate on the "salt" field. +func SaltEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldSalt, v)) +} + +// SaltContainsFold applies the ContainsFold predicate on the "salt" field. +func SaltContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldSalt, v)) +} + +// RegIPEQ applies the EQ predicate on the "reg_ip" field. +func RegIPEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldRegIP, v)) +} + +// RegIPNEQ applies the NEQ predicate on the "reg_ip" field. +func RegIPNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldRegIP, v)) +} + +// RegIPIn applies the In predicate on the "reg_ip" field. +func RegIPIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldRegIP, vs...)) +} + +// RegIPNotIn applies the NotIn predicate on the "reg_ip" field. +func RegIPNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldRegIP, vs...)) +} + +// RegIPGT applies the GT predicate on the "reg_ip" field. +func RegIPGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldRegIP, v)) +} + +// RegIPGTE applies the GTE predicate on the "reg_ip" field. +func RegIPGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldRegIP, v)) +} + +// RegIPLT applies the LT predicate on the "reg_ip" field. +func RegIPLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldRegIP, v)) +} + +// RegIPLTE applies the LTE predicate on the "reg_ip" field. +func RegIPLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldRegIP, v)) +} + +// RegIPContains applies the Contains predicate on the "reg_ip" field. +func RegIPContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldRegIP, v)) +} + +// RegIPHasPrefix applies the HasPrefix predicate on the "reg_ip" field. +func RegIPHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldRegIP, v)) +} + +// RegIPHasSuffix applies the HasSuffix predicate on the "reg_ip" field. +func RegIPHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldRegIP, v)) +} + +// RegIPEqualFold applies the EqualFold predicate on the "reg_ip" field. +func RegIPEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldRegIP, v)) +} + +// RegIPContainsFold applies the ContainsFold predicate on the "reg_ip" field. +func RegIPContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldRegIP, v)) +} + +// StateEQ applies the EQ predicate on the "state" field. +func StateEQ(v int) predicate.User { + return predicate.User(sql.FieldEQ(FieldState, v)) +} + +// StateNEQ applies the NEQ predicate on the "state" field. +func StateNEQ(v int) predicate.User { + return predicate.User(sql.FieldNEQ(FieldState, v)) +} + +// StateIn applies the In predicate on the "state" field. +func StateIn(vs ...int) predicate.User { + return predicate.User(sql.FieldIn(FieldState, vs...)) +} + +// StateNotIn applies the NotIn predicate on the "state" field. +func StateNotIn(vs ...int) predicate.User { + return predicate.User(sql.FieldNotIn(FieldState, vs...)) +} + +// StateGT applies the GT predicate on the "state" field. +func StateGT(v int) predicate.User { + return predicate.User(sql.FieldGT(FieldState, v)) +} + +// StateGTE applies the GTE predicate on the "state" field. +func StateGTE(v int) predicate.User { + return predicate.User(sql.FieldGTE(FieldState, v)) +} + +// StateLT applies the LT predicate on the "state" field. +func StateLT(v int) predicate.User { + return predicate.User(sql.FieldLT(FieldState, v)) +} + +// StateLTE applies the LTE predicate on the "state" field. +func StateLTE(v int) predicate.User { + return predicate.User(sql.FieldLTE(FieldState, v)) +} + +// RegTimeEQ applies the EQ predicate on the "reg_time" field. +func RegTimeEQ(v int64) predicate.User { + return predicate.User(sql.FieldEQ(FieldRegTime, v)) +} + +// RegTimeNEQ applies the NEQ predicate on the "reg_time" field. +func RegTimeNEQ(v int64) predicate.User { + return predicate.User(sql.FieldNEQ(FieldRegTime, v)) +} + +// RegTimeIn applies the In predicate on the "reg_time" field. +func RegTimeIn(vs ...int64) predicate.User { + return predicate.User(sql.FieldIn(FieldRegTime, vs...)) +} + +// RegTimeNotIn applies the NotIn predicate on the "reg_time" field. +func RegTimeNotIn(vs ...int64) predicate.User { + return predicate.User(sql.FieldNotIn(FieldRegTime, vs...)) +} + +// RegTimeGT applies the GT predicate on the "reg_time" field. +func RegTimeGT(v int64) predicate.User { + return predicate.User(sql.FieldGT(FieldRegTime, v)) +} + +// RegTimeGTE applies the GTE predicate on the "reg_time" field. +func RegTimeGTE(v int64) predicate.User { + return predicate.User(sql.FieldGTE(FieldRegTime, v)) +} + +// RegTimeLT applies the LT predicate on the "reg_time" field. +func RegTimeLT(v int64) predicate.User { + return predicate.User(sql.FieldLT(FieldRegTime, v)) +} + +// RegTimeLTE applies the LTE predicate on the "reg_time" field. +func RegTimeLTE(v int64) predicate.User { + return predicate.User(sql.FieldLTE(FieldRegTime, v)) +} + +// HasCreatedTexture applies the HasEdge predicate on the "created_texture" edge. +func HasCreatedTexture() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, CreatedTextureTable, CreatedTextureColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasCreatedTextureWith applies the HasEdge predicate on the "created_texture" edge with a given conditions (other predicates). +func HasCreatedTextureWith(preds ...predicate.Texture) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newCreatedTextureStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasProfile applies the HasEdge predicate on the "profile" edge. +func HasProfile() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, ProfileTable, ProfileColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasProfileWith applies the HasEdge predicate on the "profile" edge with a given conditions (other predicates). +func HasProfileWith(preds ...predicate.UserProfile) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newProfileStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasToken applies the HasEdge predicate on the "token" edge. +func HasToken() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, TokenTable, TokenColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTokenWith applies the HasEdge predicate on the "token" edge with a given conditions (other predicates). +func HasTokenWith(preds ...predicate.UserToken) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newTokenStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.User) predicate.User { + return predicate.User(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.User) predicate.User { + return predicate.User(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.User) predicate.User { + return predicate.User(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/db/ent/user_create.go b/db/ent/user_create.go new file mode 100644 index 0000000..8aa35c2 --- /dev/null +++ b/db/ent/user_create.go @@ -0,0 +1,348 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "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/db/ent/usertoken" +) + +// UserCreate is the builder for creating a User entity. +type UserCreate struct { + config + mutation *UserMutation + hooks []Hook +} + +// SetEmail sets the "email" field. +func (uc *UserCreate) SetEmail(s string) *UserCreate { + uc.mutation.SetEmail(s) + return uc +} + +// SetPassword sets the "password" field. +func (uc *UserCreate) SetPassword(s string) *UserCreate { + uc.mutation.SetPassword(s) + return uc +} + +// SetSalt sets the "salt" field. +func (uc *UserCreate) SetSalt(s string) *UserCreate { + uc.mutation.SetSalt(s) + return uc +} + +// SetRegIP sets the "reg_ip" field. +func (uc *UserCreate) SetRegIP(s string) *UserCreate { + uc.mutation.SetRegIP(s) + return uc +} + +// SetState sets the "state" field. +func (uc *UserCreate) SetState(i int) *UserCreate { + uc.mutation.SetState(i) + return uc +} + +// SetRegTime sets the "reg_time" field. +func (uc *UserCreate) SetRegTime(i int64) *UserCreate { + uc.mutation.SetRegTime(i) + return uc +} + +// AddCreatedTextureIDs adds the "created_texture" edge to the Texture entity by IDs. +func (uc *UserCreate) AddCreatedTextureIDs(ids ...int) *UserCreate { + uc.mutation.AddCreatedTextureIDs(ids...) + return uc +} + +// AddCreatedTexture adds the "created_texture" edges to the Texture entity. +func (uc *UserCreate) AddCreatedTexture(t ...*Texture) *UserCreate { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return uc.AddCreatedTextureIDs(ids...) +} + +// SetProfileID sets the "profile" edge to the UserProfile entity by ID. +func (uc *UserCreate) SetProfileID(id int) *UserCreate { + uc.mutation.SetProfileID(id) + return uc +} + +// SetNillableProfileID sets the "profile" edge to the UserProfile entity by ID if the given value is not nil. +func (uc *UserCreate) SetNillableProfileID(id *int) *UserCreate { + if id != nil { + uc = uc.SetProfileID(*id) + } + return uc +} + +// SetProfile sets the "profile" edge to the UserProfile entity. +func (uc *UserCreate) SetProfile(u *UserProfile) *UserCreate { + return uc.SetProfileID(u.ID) +} + +// SetTokenID sets the "token" edge to the UserToken entity by ID. +func (uc *UserCreate) SetTokenID(id int) *UserCreate { + uc.mutation.SetTokenID(id) + return uc +} + +// SetNillableTokenID sets the "token" edge to the UserToken entity by ID if the given value is not nil. +func (uc *UserCreate) SetNillableTokenID(id *int) *UserCreate { + if id != nil { + uc = uc.SetTokenID(*id) + } + return uc +} + +// SetToken sets the "token" edge to the UserToken entity. +func (uc *UserCreate) SetToken(u *UserToken) *UserCreate { + return uc.SetTokenID(u.ID) +} + +// Mutation returns the UserMutation object of the builder. +func (uc *UserCreate) Mutation() *UserMutation { + return uc.mutation +} + +// Save creates the User in the database. +func (uc *UserCreate) Save(ctx context.Context) (*User, error) { + return withHooks(ctx, uc.sqlSave, uc.mutation, uc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (uc *UserCreate) SaveX(ctx context.Context) *User { + v, err := uc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (uc *UserCreate) Exec(ctx context.Context) error { + _, err := uc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uc *UserCreate) ExecX(ctx context.Context) { + if err := uc.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (uc *UserCreate) check() error { + if _, ok := uc.mutation.Email(); !ok { + return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "User.email"`)} + } + if _, ok := uc.mutation.Password(); !ok { + return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "User.password"`)} + } + if _, ok := uc.mutation.Salt(); !ok { + return &ValidationError{Name: "salt", err: errors.New(`ent: missing required field "User.salt"`)} + } + if _, ok := uc.mutation.RegIP(); !ok { + return &ValidationError{Name: "reg_ip", err: errors.New(`ent: missing required field "User.reg_ip"`)} + } + if _, ok := uc.mutation.State(); !ok { + return &ValidationError{Name: "state", err: errors.New(`ent: missing required field "User.state"`)} + } + if _, ok := uc.mutation.RegTime(); !ok { + return &ValidationError{Name: "reg_time", err: errors.New(`ent: missing required field "User.reg_time"`)} + } + return nil +} + +func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) { + if err := uc.check(); err != nil { + return nil, err + } + _node, _spec := uc.createSpec() + if err := sqlgraph.CreateNode(ctx, uc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + uc.mutation.id = &_node.ID + uc.mutation.done = true + return _node, nil +} + +func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { + var ( + _node = &User{config: uc.config} + _spec = sqlgraph.NewCreateSpec(user.Table, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) + ) + if value, ok := uc.mutation.Email(); ok { + _spec.SetField(user.FieldEmail, field.TypeString, value) + _node.Email = value + } + if value, ok := uc.mutation.Password(); ok { + _spec.SetField(user.FieldPassword, field.TypeString, value) + _node.Password = value + } + if value, ok := uc.mutation.Salt(); ok { + _spec.SetField(user.FieldSalt, field.TypeString, value) + _node.Salt = value + } + if value, ok := uc.mutation.RegIP(); ok { + _spec.SetField(user.FieldRegIP, field.TypeString, value) + _node.RegIP = value + } + if value, ok := uc.mutation.State(); ok { + _spec.SetField(user.FieldState, field.TypeInt, value) + _node.State = value + } + if value, ok := uc.mutation.RegTime(); ok { + _spec.SetField(user.FieldRegTime, field.TypeInt64, value) + _node.RegTime = value + } + if nodes := uc.mutation.CreatedTextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := uc.mutation.ProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.ProfileTable, + Columns: []string{user.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := uc.mutation.TokenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.TokenTable, + Columns: []string{user.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserCreateBulk is the builder for creating many User entities in bulk. +type UserCreateBulk struct { + config + builders []*UserCreate +} + +// Save creates the User entities in the database. +func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) { + specs := make([]*sqlgraph.CreateSpec, len(ucb.builders)) + nodes := make([]*User, len(ucb.builders)) + mutators := make([]Mutator, len(ucb.builders)) + for i := range ucb.builders { + func(i int, root context.Context) { + builder := ucb.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ucb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ucb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ucb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User { + v, err := ucb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ucb *UserCreateBulk) Exec(ctx context.Context) error { + _, err := ucb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ucb *UserCreateBulk) ExecX(ctx context.Context) { + if err := ucb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/user_delete.go b/db/ent/user_delete.go new file mode 100644 index 0000000..c7c11e2 --- /dev/null +++ b/db/ent/user_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/user" +) + +// UserDelete is the builder for deleting a User entity. +type UserDelete struct { + config + hooks []Hook + mutation *UserMutation +} + +// Where appends a list predicates to the UserDelete builder. +func (ud *UserDelete) Where(ps ...predicate.User) *UserDelete { + ud.mutation.Where(ps...) + return ud +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ud *UserDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, ud.sqlExec, ud.mutation, ud.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (ud *UserDelete) ExecX(ctx context.Context) int { + n, err := ud.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ud *UserDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(user.Table, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) + if ps := ud.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ud.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + ud.mutation.done = true + return affected, err +} + +// UserDeleteOne is the builder for deleting a single User entity. +type UserDeleteOne struct { + ud *UserDelete +} + +// Where appends a list predicates to the UserDelete builder. +func (udo *UserDeleteOne) Where(ps ...predicate.User) *UserDeleteOne { + udo.ud.mutation.Where(ps...) + return udo +} + +// Exec executes the deletion query. +func (udo *UserDeleteOne) Exec(ctx context.Context) error { + n, err := udo.ud.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{user.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (udo *UserDeleteOne) ExecX(ctx context.Context) { + if err := udo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/user_query.go b/db/ent/user_query.go new file mode 100644 index 0000000..fb2b507 --- /dev/null +++ b/db/ent/user_query.go @@ -0,0 +1,805 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertoken" +) + +// UserQuery is the builder for querying User entities. +type UserQuery struct { + config + ctx *QueryContext + order []user.OrderOption + inters []Interceptor + predicates []predicate.User + withCreatedTexture *TextureQuery + withProfile *UserProfileQuery + withToken *UserTokenQuery + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserQuery builder. +func (uq *UserQuery) Where(ps ...predicate.User) *UserQuery { + uq.predicates = append(uq.predicates, ps...) + return uq +} + +// Limit the number of records to be returned by this query. +func (uq *UserQuery) Limit(limit int) *UserQuery { + uq.ctx.Limit = &limit + return uq +} + +// Offset to start from. +func (uq *UserQuery) Offset(offset int) *UserQuery { + uq.ctx.Offset = &offset + return uq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (uq *UserQuery) Unique(unique bool) *UserQuery { + uq.ctx.Unique = &unique + return uq +} + +// Order specifies how the records should be ordered. +func (uq *UserQuery) Order(o ...user.OrderOption) *UserQuery { + uq.order = append(uq.order, o...) + return uq +} + +// QueryCreatedTexture chains the current query on the "created_texture" edge. +func (uq *UserQuery) QueryCreatedTexture() *TextureQuery { + query := (&TextureClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(texture.Table, texture.FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, user.CreatedTextureTable, user.CreatedTextureColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryProfile chains the current query on the "profile" edge. +func (uq *UserQuery) QueryProfile() *UserProfileQuery { + query := (&UserProfileClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(userprofile.Table, userprofile.FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, user.ProfileTable, user.ProfileColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryToken chains the current query on the "token" edge. +func (uq *UserQuery) QueryToken() *UserTokenQuery { + query := (&UserTokenClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(usertoken.Table, usertoken.FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, user.TokenTable, user.TokenColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first User entity from the query. +// Returns a *NotFoundError when no User was found. +func (uq *UserQuery) First(ctx context.Context) (*User, error) { + nodes, err := uq.Limit(1).All(setContextOp(ctx, uq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{user.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (uq *UserQuery) FirstX(ctx context.Context) *User { + node, err := uq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first User ID from the query. +// Returns a *NotFoundError when no User ID was found. +func (uq *UserQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = uq.Limit(1).IDs(setContextOp(ctx, uq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{user.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (uq *UserQuery) FirstIDX(ctx context.Context) int { + id, err := uq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single User entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one User entity is found. +// Returns a *NotFoundError when no User entities are found. +func (uq *UserQuery) Only(ctx context.Context) (*User, error) { + nodes, err := uq.Limit(2).All(setContextOp(ctx, uq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{user.Label} + default: + return nil, &NotSingularError{user.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (uq *UserQuery) OnlyX(ctx context.Context) *User { + node, err := uq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only User ID in the query. +// Returns a *NotSingularError when more than one User ID is found. +// Returns a *NotFoundError when no entities are found. +func (uq *UserQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = uq.Limit(2).IDs(setContextOp(ctx, uq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{user.Label} + default: + err = &NotSingularError{user.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (uq *UserQuery) OnlyIDX(ctx context.Context) int { + id, err := uq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Users. +func (uq *UserQuery) All(ctx context.Context) ([]*User, error) { + ctx = setContextOp(ctx, uq.ctx, "All") + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*User, *UserQuery]() + return withInterceptors[[]*User](ctx, uq, qr, uq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (uq *UserQuery) AllX(ctx context.Context) []*User { + nodes, err := uq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of User IDs. +func (uq *UserQuery) IDs(ctx context.Context) (ids []int, err error) { + if uq.ctx.Unique == nil && uq.path != nil { + uq.Unique(true) + } + ctx = setContextOp(ctx, uq.ctx, "IDs") + if err = uq.Select(user.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (uq *UserQuery) IDsX(ctx context.Context) []int { + ids, err := uq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (uq *UserQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, uq.ctx, "Count") + if err := uq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, uq, querierCount[*UserQuery](), uq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (uq *UserQuery) CountX(ctx context.Context) int { + count, err := uq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (uq *UserQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, uq.ctx, "Exist") + switch _, err := uq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (uq *UserQuery) ExistX(ctx context.Context) bool { + exist, err := uq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (uq *UserQuery) Clone() *UserQuery { + if uq == nil { + return nil + } + return &UserQuery{ + config: uq.config, + ctx: uq.ctx.Clone(), + order: append([]user.OrderOption{}, uq.order...), + inters: append([]Interceptor{}, uq.inters...), + predicates: append([]predicate.User{}, uq.predicates...), + withCreatedTexture: uq.withCreatedTexture.Clone(), + withProfile: uq.withProfile.Clone(), + withToken: uq.withToken.Clone(), + // clone intermediate query. + sql: uq.sql.Clone(), + path: uq.path, + } +} + +// WithCreatedTexture tells the query-builder to eager-load the nodes that are connected to +// the "created_texture" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithCreatedTexture(opts ...func(*TextureQuery)) *UserQuery { + query := (&TextureClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withCreatedTexture = query + return uq +} + +// WithProfile tells the query-builder to eager-load the nodes that are connected to +// the "profile" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithProfile(opts ...func(*UserProfileQuery)) *UserQuery { + query := (&UserProfileClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withProfile = query + return uq +} + +// WithToken tells the query-builder to eager-load the nodes that are connected to +// the "token" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithToken(opts ...func(*UserTokenQuery)) *UserQuery { + query := (&UserTokenClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withToken = query + return uq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Email string `json:"email,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.User.Query(). +// GroupBy(user.FieldEmail). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (uq *UserQuery) GroupBy(field string, fields ...string) *UserGroupBy { + uq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserGroupBy{build: uq} + grbuild.flds = &uq.ctx.Fields + grbuild.label = user.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Email string `json:"email,omitempty"` +// } +// +// client.User.Query(). +// Select(user.FieldEmail). +// Scan(ctx, &v) +func (uq *UserQuery) Select(fields ...string) *UserSelect { + uq.ctx.Fields = append(uq.ctx.Fields, fields...) + sbuild := &UserSelect{UserQuery: uq} + sbuild.label = user.Label + sbuild.flds, sbuild.scan = &uq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserSelect configured with the given aggregations. +func (uq *UserQuery) Aggregate(fns ...AggregateFunc) *UserSelect { + return uq.Select().Aggregate(fns...) +} + +func (uq *UserQuery) prepareQuery(ctx context.Context) error { + for _, inter := range uq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, uq); err != nil { + return err + } + } + } + for _, f := range uq.ctx.Fields { + if !user.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if uq.path != nil { + prev, err := uq.path(ctx) + if err != nil { + return err + } + uq.sql = prev + } + return nil +} + +func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, error) { + var ( + nodes = []*User{} + _spec = uq.querySpec() + loadedTypes = [3]bool{ + uq.withCreatedTexture != nil, + uq.withProfile != nil, + uq.withToken != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*User).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &User{config: uq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(uq.modifiers) > 0 { + _spec.Modifiers = uq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, uq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := uq.withCreatedTexture; query != nil { + if err := uq.loadCreatedTexture(ctx, query, nodes, + func(n *User) { n.Edges.CreatedTexture = []*Texture{} }, + func(n *User, e *Texture) { n.Edges.CreatedTexture = append(n.Edges.CreatedTexture, e) }); err != nil { + return nil, err + } + } + if query := uq.withProfile; query != nil { + if err := uq.loadProfile(ctx, query, nodes, nil, + func(n *User, e *UserProfile) { n.Edges.Profile = e }); err != nil { + return nil, err + } + } + if query := uq.withToken; query != nil { + if err := uq.loadToken(ctx, query, nodes, nil, + func(n *User, e *UserToken) { n.Edges.Token = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (uq *UserQuery) loadCreatedTexture(ctx context.Context, query *TextureQuery, nodes []*User, init func(*User), assign func(*User, *Texture)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.withFKs = true + query.Where(predicate.Texture(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.CreatedTextureColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.texture_created_user + if fk == nil { + return fmt.Errorf(`foreign-key "texture_created_user" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "texture_created_user" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} +func (uq *UserQuery) loadProfile(ctx context.Context, query *UserProfileQuery, nodes []*User, init func(*User), assign func(*User, *UserProfile)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + } + query.withFKs = true + query.Where(predicate.UserProfile(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.ProfileColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.user_profile + if fk == nil { + return fmt.Errorf(`foreign-key "user_profile" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_profile" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} +func (uq *UserQuery) loadToken(ctx context.Context, query *UserTokenQuery, nodes []*User, init func(*User), assign func(*User, *UserToken)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + } + query.withFKs = true + query.Where(predicate.UserToken(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.TokenColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.user_token + if fk == nil { + return fmt.Errorf(`foreign-key "user_token" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_token" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { + _spec := uq.querySpec() + if len(uq.modifiers) > 0 { + _spec.Modifiers = uq.modifiers + } + _spec.Node.Columns = uq.ctx.Fields + if len(uq.ctx.Fields) > 0 { + _spec.Unique = uq.ctx.Unique != nil && *uq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, uq.driver, _spec) +} + +func (uq *UserQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) + _spec.From = uq.sql + if unique := uq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if uq.path != nil { + _spec.Unique = true + } + if fields := uq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) + for i := range fields { + if fields[i] != user.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := uq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := uq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := uq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := uq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (uq *UserQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(uq.driver.Dialect()) + t1 := builder.Table(user.Table) + columns := uq.ctx.Fields + if len(columns) == 0 { + columns = user.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if uq.sql != nil { + selector = uq.sql + selector.Select(selector.Columns(columns...)...) + } + if uq.ctx.Unique != nil && *uq.ctx.Unique { + selector.Distinct() + } + for _, m := range uq.modifiers { + m(selector) + } + for _, p := range uq.predicates { + p(selector) + } + for _, p := range uq.order { + p(selector) + } + if offset := uq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := uq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (uq *UserQuery) ForUpdate(opts ...sql.LockOption) *UserQuery { + if uq.driver.Dialect() == dialect.Postgres { + uq.Unique(false) + } + uq.modifiers = append(uq.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return uq +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (uq *UserQuery) ForShare(opts ...sql.LockOption) *UserQuery { + if uq.driver.Dialect() == dialect.Postgres { + uq.Unique(false) + } + uq.modifiers = append(uq.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return uq +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (uq *UserQuery) ForUpdateA(opts ...sql.LockOption) *UserQuery { + if uq.driver.Dialect() == dialect.SQLite { + return uq + } + return uq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (uq *UserQuery) ForShareA(opts ...sql.LockOption) *UserQuery { + if uq.driver.Dialect() == dialect.SQLite { + return uq + } + return uq.ForShare(opts...) +} + +// UserGroupBy is the group-by builder for User entities. +type UserGroupBy struct { + selector + build *UserQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (ugb *UserGroupBy) Aggregate(fns ...AggregateFunc) *UserGroupBy { + ugb.fns = append(ugb.fns, fns...) + return ugb +} + +// Scan applies the selector query and scans the result into the given value. +func (ugb *UserGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ugb.build.ctx, "GroupBy") + if err := ugb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserQuery, *UserGroupBy](ctx, ugb.build, ugb, ugb.build.inters, v) +} + +func (ugb *UserGroupBy) sqlScan(ctx context.Context, root *UserQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(ugb.fns)) + for _, fn := range ugb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*ugb.flds)+len(ugb.fns)) + for _, f := range *ugb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*ugb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ugb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserSelect is the builder for selecting fields of User entities. +type UserSelect struct { + *UserQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (us *UserSelect) Aggregate(fns ...AggregateFunc) *UserSelect { + us.fns = append(us.fns, fns...) + return us +} + +// Scan applies the selector query and scans the result into the given value. +func (us *UserSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, us.ctx, "Select") + if err := us.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserQuery, *UserSelect](ctx, us.UserQuery, us, us.inters, v) +} + +func (us *UserSelect) sqlScan(ctx context.Context, root *UserQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(us.fns)) + for _, fn := range us.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*us.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := us.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/db/ent/user_update.go b/db/ent/user_update.go new file mode 100644 index 0000000..9a9141f --- /dev/null +++ b/db/ent/user_update.go @@ -0,0 +1,704 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertoken" +) + +// UserUpdate is the builder for updating User entities. +type UserUpdate struct { + config + hooks []Hook + mutation *UserMutation +} + +// Where appends a list predicates to the UserUpdate builder. +func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { + uu.mutation.Where(ps...) + return uu +} + +// SetEmail sets the "email" field. +func (uu *UserUpdate) SetEmail(s string) *UserUpdate { + uu.mutation.SetEmail(s) + return uu +} + +// SetPassword sets the "password" field. +func (uu *UserUpdate) SetPassword(s string) *UserUpdate { + uu.mutation.SetPassword(s) + return uu +} + +// SetSalt sets the "salt" field. +func (uu *UserUpdate) SetSalt(s string) *UserUpdate { + uu.mutation.SetSalt(s) + return uu +} + +// SetRegIP sets the "reg_ip" field. +func (uu *UserUpdate) SetRegIP(s string) *UserUpdate { + uu.mutation.SetRegIP(s) + return uu +} + +// SetState sets the "state" field. +func (uu *UserUpdate) SetState(i int) *UserUpdate { + uu.mutation.ResetState() + uu.mutation.SetState(i) + return uu +} + +// AddState adds i to the "state" field. +func (uu *UserUpdate) AddState(i int) *UserUpdate { + uu.mutation.AddState(i) + return uu +} + +// SetRegTime sets the "reg_time" field. +func (uu *UserUpdate) SetRegTime(i int64) *UserUpdate { + uu.mutation.ResetRegTime() + uu.mutation.SetRegTime(i) + return uu +} + +// AddRegTime adds i to the "reg_time" field. +func (uu *UserUpdate) AddRegTime(i int64) *UserUpdate { + uu.mutation.AddRegTime(i) + return uu +} + +// AddCreatedTextureIDs adds the "created_texture" edge to the Texture entity by IDs. +func (uu *UserUpdate) AddCreatedTextureIDs(ids ...int) *UserUpdate { + uu.mutation.AddCreatedTextureIDs(ids...) + return uu +} + +// AddCreatedTexture adds the "created_texture" edges to the Texture entity. +func (uu *UserUpdate) AddCreatedTexture(t ...*Texture) *UserUpdate { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return uu.AddCreatedTextureIDs(ids...) +} + +// SetProfileID sets the "profile" edge to the UserProfile entity by ID. +func (uu *UserUpdate) SetProfileID(id int) *UserUpdate { + uu.mutation.SetProfileID(id) + return uu +} + +// SetNillableProfileID sets the "profile" edge to the UserProfile entity by ID if the given value is not nil. +func (uu *UserUpdate) SetNillableProfileID(id *int) *UserUpdate { + if id != nil { + uu = uu.SetProfileID(*id) + } + return uu +} + +// SetProfile sets the "profile" edge to the UserProfile entity. +func (uu *UserUpdate) SetProfile(u *UserProfile) *UserUpdate { + return uu.SetProfileID(u.ID) +} + +// SetTokenID sets the "token" edge to the UserToken entity by ID. +func (uu *UserUpdate) SetTokenID(id int) *UserUpdate { + uu.mutation.SetTokenID(id) + return uu +} + +// SetNillableTokenID sets the "token" edge to the UserToken entity by ID if the given value is not nil. +func (uu *UserUpdate) SetNillableTokenID(id *int) *UserUpdate { + if id != nil { + uu = uu.SetTokenID(*id) + } + return uu +} + +// SetToken sets the "token" edge to the UserToken entity. +func (uu *UserUpdate) SetToken(u *UserToken) *UserUpdate { + return uu.SetTokenID(u.ID) +} + +// Mutation returns the UserMutation object of the builder. +func (uu *UserUpdate) Mutation() *UserMutation { + return uu.mutation +} + +// ClearCreatedTexture clears all "created_texture" edges to the Texture entity. +func (uu *UserUpdate) ClearCreatedTexture() *UserUpdate { + uu.mutation.ClearCreatedTexture() + return uu +} + +// RemoveCreatedTextureIDs removes the "created_texture" edge to Texture entities by IDs. +func (uu *UserUpdate) RemoveCreatedTextureIDs(ids ...int) *UserUpdate { + uu.mutation.RemoveCreatedTextureIDs(ids...) + return uu +} + +// RemoveCreatedTexture removes "created_texture" edges to Texture entities. +func (uu *UserUpdate) RemoveCreatedTexture(t ...*Texture) *UserUpdate { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return uu.RemoveCreatedTextureIDs(ids...) +} + +// ClearProfile clears the "profile" edge to the UserProfile entity. +func (uu *UserUpdate) ClearProfile() *UserUpdate { + uu.mutation.ClearProfile() + return uu +} + +// ClearToken clears the "token" edge to the UserToken entity. +func (uu *UserUpdate) ClearToken() *UserUpdate { + uu.mutation.ClearToken() + return uu +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (uu *UserUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, uu.sqlSave, uu.mutation, uu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (uu *UserUpdate) SaveX(ctx context.Context) int { + affected, err := uu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (uu *UserUpdate) Exec(ctx context.Context) error { + _, err := uu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uu *UserUpdate) ExecX(ctx context.Context) { + if err := uu.Exec(ctx); err != nil { + panic(err) + } +} + +func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) + if ps := uu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := uu.mutation.Email(); ok { + _spec.SetField(user.FieldEmail, field.TypeString, value) + } + if value, ok := uu.mutation.Password(); ok { + _spec.SetField(user.FieldPassword, field.TypeString, value) + } + if value, ok := uu.mutation.Salt(); ok { + _spec.SetField(user.FieldSalt, field.TypeString, value) + } + if value, ok := uu.mutation.RegIP(); ok { + _spec.SetField(user.FieldRegIP, field.TypeString, value) + } + if value, ok := uu.mutation.State(); ok { + _spec.SetField(user.FieldState, field.TypeInt, value) + } + if value, ok := uu.mutation.AddedState(); ok { + _spec.AddField(user.FieldState, field.TypeInt, value) + } + if value, ok := uu.mutation.RegTime(); ok { + _spec.SetField(user.FieldRegTime, field.TypeInt64, value) + } + if value, ok := uu.mutation.AddedRegTime(); ok { + _spec.AddField(user.FieldRegTime, field.TypeInt64, value) + } + if uu.mutation.CreatedTextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedCreatedTextureIDs(); len(nodes) > 0 && !uu.mutation.CreatedTextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.CreatedTextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uu.mutation.ProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.ProfileTable, + Columns: []string{user.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.ProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.ProfileTable, + Columns: []string{user.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uu.mutation.TokenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.TokenTable, + Columns: []string{user.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.TokenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.TokenTable, + Columns: []string{user.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{user.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + uu.mutation.done = true + return n, nil +} + +// UserUpdateOne is the builder for updating a single User entity. +type UserUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserMutation +} + +// SetEmail sets the "email" field. +func (uuo *UserUpdateOne) SetEmail(s string) *UserUpdateOne { + uuo.mutation.SetEmail(s) + return uuo +} + +// SetPassword sets the "password" field. +func (uuo *UserUpdateOne) SetPassword(s string) *UserUpdateOne { + uuo.mutation.SetPassword(s) + return uuo +} + +// SetSalt sets the "salt" field. +func (uuo *UserUpdateOne) SetSalt(s string) *UserUpdateOne { + uuo.mutation.SetSalt(s) + return uuo +} + +// SetRegIP sets the "reg_ip" field. +func (uuo *UserUpdateOne) SetRegIP(s string) *UserUpdateOne { + uuo.mutation.SetRegIP(s) + return uuo +} + +// SetState sets the "state" field. +func (uuo *UserUpdateOne) SetState(i int) *UserUpdateOne { + uuo.mutation.ResetState() + uuo.mutation.SetState(i) + return uuo +} + +// AddState adds i to the "state" field. +func (uuo *UserUpdateOne) AddState(i int) *UserUpdateOne { + uuo.mutation.AddState(i) + return uuo +} + +// SetRegTime sets the "reg_time" field. +func (uuo *UserUpdateOne) SetRegTime(i int64) *UserUpdateOne { + uuo.mutation.ResetRegTime() + uuo.mutation.SetRegTime(i) + return uuo +} + +// AddRegTime adds i to the "reg_time" field. +func (uuo *UserUpdateOne) AddRegTime(i int64) *UserUpdateOne { + uuo.mutation.AddRegTime(i) + return uuo +} + +// AddCreatedTextureIDs adds the "created_texture" edge to the Texture entity by IDs. +func (uuo *UserUpdateOne) AddCreatedTextureIDs(ids ...int) *UserUpdateOne { + uuo.mutation.AddCreatedTextureIDs(ids...) + return uuo +} + +// AddCreatedTexture adds the "created_texture" edges to the Texture entity. +func (uuo *UserUpdateOne) AddCreatedTexture(t ...*Texture) *UserUpdateOne { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return uuo.AddCreatedTextureIDs(ids...) +} + +// SetProfileID sets the "profile" edge to the UserProfile entity by ID. +func (uuo *UserUpdateOne) SetProfileID(id int) *UserUpdateOne { + uuo.mutation.SetProfileID(id) + return uuo +} + +// SetNillableProfileID sets the "profile" edge to the UserProfile entity by ID if the given value is not nil. +func (uuo *UserUpdateOne) SetNillableProfileID(id *int) *UserUpdateOne { + if id != nil { + uuo = uuo.SetProfileID(*id) + } + return uuo +} + +// SetProfile sets the "profile" edge to the UserProfile entity. +func (uuo *UserUpdateOne) SetProfile(u *UserProfile) *UserUpdateOne { + return uuo.SetProfileID(u.ID) +} + +// SetTokenID sets the "token" edge to the UserToken entity by ID. +func (uuo *UserUpdateOne) SetTokenID(id int) *UserUpdateOne { + uuo.mutation.SetTokenID(id) + return uuo +} + +// SetNillableTokenID sets the "token" edge to the UserToken entity by ID if the given value is not nil. +func (uuo *UserUpdateOne) SetNillableTokenID(id *int) *UserUpdateOne { + if id != nil { + uuo = uuo.SetTokenID(*id) + } + return uuo +} + +// SetToken sets the "token" edge to the UserToken entity. +func (uuo *UserUpdateOne) SetToken(u *UserToken) *UserUpdateOne { + return uuo.SetTokenID(u.ID) +} + +// Mutation returns the UserMutation object of the builder. +func (uuo *UserUpdateOne) Mutation() *UserMutation { + return uuo.mutation +} + +// ClearCreatedTexture clears all "created_texture" edges to the Texture entity. +func (uuo *UserUpdateOne) ClearCreatedTexture() *UserUpdateOne { + uuo.mutation.ClearCreatedTexture() + return uuo +} + +// RemoveCreatedTextureIDs removes the "created_texture" edge to Texture entities by IDs. +func (uuo *UserUpdateOne) RemoveCreatedTextureIDs(ids ...int) *UserUpdateOne { + uuo.mutation.RemoveCreatedTextureIDs(ids...) + return uuo +} + +// RemoveCreatedTexture removes "created_texture" edges to Texture entities. +func (uuo *UserUpdateOne) RemoveCreatedTexture(t ...*Texture) *UserUpdateOne { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return uuo.RemoveCreatedTextureIDs(ids...) +} + +// ClearProfile clears the "profile" edge to the UserProfile entity. +func (uuo *UserUpdateOne) ClearProfile() *UserUpdateOne { + uuo.mutation.ClearProfile() + return uuo +} + +// ClearToken clears the "token" edge to the UserToken entity. +func (uuo *UserUpdateOne) ClearToken() *UserUpdateOne { + uuo.mutation.ClearToken() + return uuo +} + +// Where appends a list predicates to the UserUpdate builder. +func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { + uuo.mutation.Where(ps...) + return uuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (uuo *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne { + uuo.fields = append([]string{field}, fields...) + return uuo +} + +// Save executes the query and returns the updated User entity. +func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) { + return withHooks(ctx, uuo.sqlSave, uuo.mutation, uuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User { + node, err := uuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (uuo *UserUpdateOne) Exec(ctx context.Context) error { + _, err := uuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (uuo *UserUpdateOne) ExecX(ctx context.Context) { + if err := uuo.Exec(ctx); err != nil { + panic(err) + } +} + +func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { + _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) + id, ok := uuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "User.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := uuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) + for _, f := range fields { + if !user.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != user.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := uuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := uuo.mutation.Email(); ok { + _spec.SetField(user.FieldEmail, field.TypeString, value) + } + if value, ok := uuo.mutation.Password(); ok { + _spec.SetField(user.FieldPassword, field.TypeString, value) + } + if value, ok := uuo.mutation.Salt(); ok { + _spec.SetField(user.FieldSalt, field.TypeString, value) + } + if value, ok := uuo.mutation.RegIP(); ok { + _spec.SetField(user.FieldRegIP, field.TypeString, value) + } + if value, ok := uuo.mutation.State(); ok { + _spec.SetField(user.FieldState, field.TypeInt, value) + } + if value, ok := uuo.mutation.AddedState(); ok { + _spec.AddField(user.FieldState, field.TypeInt, value) + } + if value, ok := uuo.mutation.RegTime(); ok { + _spec.SetField(user.FieldRegTime, field.TypeInt64, value) + } + if value, ok := uuo.mutation.AddedRegTime(); ok { + _spec.AddField(user.FieldRegTime, field.TypeInt64, value) + } + if uuo.mutation.CreatedTextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedCreatedTextureIDs(); len(nodes) > 0 && !uuo.mutation.CreatedTextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.CreatedTextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: user.CreatedTextureTable, + Columns: []string{user.CreatedTextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uuo.mutation.ProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.ProfileTable, + Columns: []string{user.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.ProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.ProfileTable, + Columns: []string{user.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if uuo.mutation.TokenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.TokenTable, + Columns: []string{user.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.TokenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: user.TokenTable, + Columns: []string{user.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &User{config: uuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, uuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{user.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + uuo.mutation.done = true + return _node, nil +} diff --git a/db/ent/userprofile.go b/db/ent/userprofile.go new file mode 100644 index 0000000..f59359c --- /dev/null +++ b/db/ent/userprofile.go @@ -0,0 +1,187 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" +) + +// UserProfile is the model entity for the UserProfile schema. +type UserProfile struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // UUID holds the value of the "uuid" field. + UUID string `json:"uuid,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserProfileQuery when eager-loading is set. + Edges UserProfileEdges `json:"edges"` + user_profile *int + selectValues sql.SelectValues +} + +// UserProfileEdges holds the relations/edges for other nodes in the graph. +type UserProfileEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // Texture holds the value of the texture edge. + Texture []*Texture `json:"texture,omitempty"` + // Usertexture holds the value of the usertexture edge. + Usertexture []*UserTexture `json:"usertexture,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserProfileEdges) UserOrErr() (*User, error) { + if e.loadedTypes[0] { + if e.User == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: user.Label} + } + return e.User, nil + } + return nil, &NotLoadedError{edge: "user"} +} + +// TextureOrErr returns the Texture value or an error if the edge +// was not loaded in eager-loading. +func (e UserProfileEdges) TextureOrErr() ([]*Texture, error) { + if e.loadedTypes[1] { + return e.Texture, nil + } + return nil, &NotLoadedError{edge: "texture"} +} + +// UsertextureOrErr returns the Usertexture value or an error if the edge +// was not loaded in eager-loading. +func (e UserProfileEdges) UsertextureOrErr() ([]*UserTexture, error) { + if e.loadedTypes[2] { + return e.Usertexture, nil + } + return nil, &NotLoadedError{edge: "usertexture"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserProfile) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case userprofile.FieldID: + values[i] = new(sql.NullInt64) + case userprofile.FieldName, userprofile.FieldUUID: + values[i] = new(sql.NullString) + case userprofile.ForeignKeys[0]: // user_profile + values[i] = new(sql.NullInt64) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserProfile fields. +func (up *UserProfile) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case userprofile.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + up.ID = int(value.Int64) + case userprofile.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + up.Name = value.String + } + case userprofile.FieldUUID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field uuid", values[i]) + } else if value.Valid { + up.UUID = value.String + } + case userprofile.ForeignKeys[0]: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for edge-field user_profile", value) + } else if value.Valid { + up.user_profile = new(int) + *up.user_profile = int(value.Int64) + } + default: + up.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserProfile. +// This includes values selected through modifiers, order, etc. +func (up *UserProfile) Value(name string) (ent.Value, error) { + return up.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the UserProfile entity. +func (up *UserProfile) QueryUser() *UserQuery { + return NewUserProfileClient(up.config).QueryUser(up) +} + +// QueryTexture queries the "texture" edge of the UserProfile entity. +func (up *UserProfile) QueryTexture() *TextureQuery { + return NewUserProfileClient(up.config).QueryTexture(up) +} + +// QueryUsertexture queries the "usertexture" edge of the UserProfile entity. +func (up *UserProfile) QueryUsertexture() *UserTextureQuery { + return NewUserProfileClient(up.config).QueryUsertexture(up) +} + +// Update returns a builder for updating this UserProfile. +// Note that you need to call UserProfile.Unwrap() before calling this method if this UserProfile +// was returned from a transaction, and the transaction was committed or rolled back. +func (up *UserProfile) Update() *UserProfileUpdateOne { + return NewUserProfileClient(up.config).UpdateOne(up) +} + +// Unwrap unwraps the UserProfile entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (up *UserProfile) Unwrap() *UserProfile { + _tx, ok := up.config.driver.(*txDriver) + if !ok { + panic("ent: UserProfile is not a transactional entity") + } + up.config.driver = _tx.drv + return up +} + +// String implements the fmt.Stringer. +func (up *UserProfile) String() string { + var builder strings.Builder + builder.WriteString("UserProfile(") + builder.WriteString(fmt.Sprintf("id=%v, ", up.ID)) + builder.WriteString("name=") + builder.WriteString(up.Name) + builder.WriteString(", ") + builder.WriteString("uuid=") + builder.WriteString(up.UUID) + builder.WriteByte(')') + return builder.String() +} + +// UserProfiles is a parsable slice of UserProfile. +type UserProfiles []*UserProfile diff --git a/db/ent/userprofile/userprofile.go b/db/ent/userprofile/userprofile.go new file mode 100644 index 0000000..f170ba8 --- /dev/null +++ b/db/ent/userprofile/userprofile.go @@ -0,0 +1,154 @@ +// Code generated by ent, DO NOT EDIT. + +package userprofile + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the userprofile type in the database. + Label = "user_profile" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldUUID holds the string denoting the uuid field in the database. + FieldUUID = "uuid" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // EdgeTexture holds the string denoting the texture edge name in mutations. + EdgeTexture = "texture" + // EdgeUsertexture holds the string denoting the usertexture edge name in mutations. + EdgeUsertexture = "usertexture" + // Table holds the table name of the userprofile in the database. + Table = "user_profiles" + // UserTable is the table that holds the user relation/edge. + UserTable = "user_profiles" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_profile" + // TextureTable is the table that holds the texture relation/edge. The primary key declared below. + TextureTable = "user_textures" + // TextureInverseTable is the table name for the Texture entity. + // It exists in this package in order to avoid circular dependency with the "texture" package. + TextureInverseTable = "textures" + // UsertextureTable is the table that holds the usertexture relation/edge. + UsertextureTable = "user_textures" + // UsertextureInverseTable is the table name for the UserTexture entity. + // It exists in this package in order to avoid circular dependency with the "usertexture" package. + UsertextureInverseTable = "user_textures" + // UsertextureColumn is the table column denoting the usertexture relation/edge. + UsertextureColumn = "user_profile_id" +) + +// Columns holds all SQL columns for userprofile fields. +var Columns = []string{ + FieldID, + FieldName, + FieldUUID, +} + +// ForeignKeys holds the SQL foreign-keys that are owned by the "user_profiles" +// table and are not defined as standalone fields in the schema. +var ForeignKeys = []string{ + "user_profile", +} + +var ( + // TexturePrimaryKey and TextureColumn2 are the table columns denoting the + // primary key for the texture relation (M2M). + TexturePrimaryKey = []string{"texture_id", "user_profile_id"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + for i := range ForeignKeys { + if column == ForeignKeys[i] { + return true + } + } + return false +} + +// OrderOption defines the ordering options for the UserProfile queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByUUID orders the results by the uuid field. +func ByUUID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUUID, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} + +// ByTextureCount orders the results by texture count. +func ByTextureCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newTextureStep(), opts...) + } +} + +// ByTexture orders the results by texture terms. +func ByTexture(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTextureStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByUsertextureCount orders the results by usertexture count. +func ByUsertextureCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newUsertextureStep(), opts...) + } +} + +// ByUsertexture orders the results by usertexture terms. +func ByUsertexture(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUsertextureStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, UserTable, UserColumn), + ) +} +func newTextureStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TextureInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, TextureTable, TexturePrimaryKey...), + ) +} +func newUsertextureStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UsertextureInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, UsertextureTable, UsertextureColumn), + ) +} diff --git a/db/ent/userprofile/where.go b/db/ent/userprofile/where.go new file mode 100644 index 0000000..01c4b1a --- /dev/null +++ b/db/ent/userprofile/where.go @@ -0,0 +1,295 @@ +// Code generated by ent, DO NOT EDIT. + +package userprofile + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/xmdhs/authlib-skin/db/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.UserProfile { + return predicate.UserProfile(sql.FieldLTE(FieldID, id)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEQ(FieldName, v)) +} + +// UUID applies equality check predicate on the "uuid" field. It's identical to UUIDEQ. +func UUID(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEQ(FieldUUID, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldContainsFold(FieldName, v)) +} + +// UUIDEQ applies the EQ predicate on the "uuid" field. +func UUIDEQ(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEQ(FieldUUID, v)) +} + +// UUIDNEQ applies the NEQ predicate on the "uuid" field. +func UUIDNEQ(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldNEQ(FieldUUID, v)) +} + +// UUIDIn applies the In predicate on the "uuid" field. +func UUIDIn(vs ...string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldIn(FieldUUID, vs...)) +} + +// UUIDNotIn applies the NotIn predicate on the "uuid" field. +func UUIDNotIn(vs ...string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldNotIn(FieldUUID, vs...)) +} + +// UUIDGT applies the GT predicate on the "uuid" field. +func UUIDGT(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldGT(FieldUUID, v)) +} + +// UUIDGTE applies the GTE predicate on the "uuid" field. +func UUIDGTE(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldGTE(FieldUUID, v)) +} + +// UUIDLT applies the LT predicate on the "uuid" field. +func UUIDLT(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldLT(FieldUUID, v)) +} + +// UUIDLTE applies the LTE predicate on the "uuid" field. +func UUIDLTE(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldLTE(FieldUUID, v)) +} + +// UUIDContains applies the Contains predicate on the "uuid" field. +func UUIDContains(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldContains(FieldUUID, v)) +} + +// UUIDHasPrefix applies the HasPrefix predicate on the "uuid" field. +func UUIDHasPrefix(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldHasPrefix(FieldUUID, v)) +} + +// UUIDHasSuffix applies the HasSuffix predicate on the "uuid" field. +func UUIDHasSuffix(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldHasSuffix(FieldUUID, v)) +} + +// UUIDEqualFold applies the EqualFold predicate on the "uuid" field. +func UUIDEqualFold(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldEqualFold(FieldUUID, v)) +} + +// UUIDContainsFold applies the ContainsFold predicate on the "uuid" field. +func UUIDContainsFold(v string) predicate.UserProfile { + return predicate.UserProfile(sql.FieldContainsFold(FieldUUID, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasTexture applies the HasEdge predicate on the "texture" edge. +func HasTexture() predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, TextureTable, TexturePrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTextureWith applies the HasEdge predicate on the "texture" edge with a given conditions (other predicates). +func HasTextureWith(preds ...predicate.Texture) predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + step := newTextureStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasUsertexture applies the HasEdge predicate on the "usertexture" edge. +func HasUsertexture() predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, UsertextureTable, UsertextureColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUsertextureWith applies the HasEdge predicate on the "usertexture" edge with a given conditions (other predicates). +func HasUsertextureWith(preds ...predicate.UserTexture) predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + step := newUsertextureStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserProfile) predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserProfile) predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserProfile) predicate.UserProfile { + return predicate.UserProfile(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/db/ent/userprofile_create.go b/db/ent/userprofile_create.go new file mode 100644 index 0000000..6b17bc8 --- /dev/null +++ b/db/ent/userprofile_create.go @@ -0,0 +1,288 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "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/db/ent/usertexture" +) + +// UserProfileCreate is the builder for creating a UserProfile entity. +type UserProfileCreate struct { + config + mutation *UserProfileMutation + hooks []Hook +} + +// SetName sets the "name" field. +func (upc *UserProfileCreate) SetName(s string) *UserProfileCreate { + upc.mutation.SetName(s) + return upc +} + +// SetUUID sets the "uuid" field. +func (upc *UserProfileCreate) SetUUID(s string) *UserProfileCreate { + upc.mutation.SetUUID(s) + return upc +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (upc *UserProfileCreate) SetUserID(id int) *UserProfileCreate { + upc.mutation.SetUserID(id) + return upc +} + +// SetUser sets the "user" edge to the User entity. +func (upc *UserProfileCreate) SetUser(u *User) *UserProfileCreate { + return upc.SetUserID(u.ID) +} + +// AddTextureIDs adds the "texture" edge to the Texture entity by IDs. +func (upc *UserProfileCreate) AddTextureIDs(ids ...int) *UserProfileCreate { + upc.mutation.AddTextureIDs(ids...) + return upc +} + +// AddTexture adds the "texture" edges to the Texture entity. +func (upc *UserProfileCreate) AddTexture(t ...*Texture) *UserProfileCreate { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return upc.AddTextureIDs(ids...) +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by IDs. +func (upc *UserProfileCreate) AddUsertextureIDs(ids ...int) *UserProfileCreate { + upc.mutation.AddUsertextureIDs(ids...) + return upc +} + +// AddUsertexture adds the "usertexture" edges to the UserTexture entity. +func (upc *UserProfileCreate) AddUsertexture(u ...*UserTexture) *UserProfileCreate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return upc.AddUsertextureIDs(ids...) +} + +// Mutation returns the UserProfileMutation object of the builder. +func (upc *UserProfileCreate) Mutation() *UserProfileMutation { + return upc.mutation +} + +// Save creates the UserProfile in the database. +func (upc *UserProfileCreate) Save(ctx context.Context) (*UserProfile, error) { + return withHooks(ctx, upc.sqlSave, upc.mutation, upc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (upc *UserProfileCreate) SaveX(ctx context.Context) *UserProfile { + v, err := upc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (upc *UserProfileCreate) Exec(ctx context.Context) error { + _, err := upc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (upc *UserProfileCreate) ExecX(ctx context.Context) { + if err := upc.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (upc *UserProfileCreate) check() error { + if _, ok := upc.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "UserProfile.name"`)} + } + if _, ok := upc.mutation.UUID(); !ok { + return &ValidationError{Name: "uuid", err: errors.New(`ent: missing required field "UserProfile.uuid"`)} + } + if _, ok := upc.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "UserProfile.user"`)} + } + return nil +} + +func (upc *UserProfileCreate) sqlSave(ctx context.Context) (*UserProfile, error) { + if err := upc.check(); err != nil { + return nil, err + } + _node, _spec := upc.createSpec() + if err := sqlgraph.CreateNode(ctx, upc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + upc.mutation.id = &_node.ID + upc.mutation.done = true + return _node, nil +} + +func (upc *UserProfileCreate) createSpec() (*UserProfile, *sqlgraph.CreateSpec) { + var ( + _node = &UserProfile{config: upc.config} + _spec = sqlgraph.NewCreateSpec(userprofile.Table, sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt)) + ) + if value, ok := upc.mutation.Name(); ok { + _spec.SetField(userprofile.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := upc.mutation.UUID(); ok { + _spec.SetField(userprofile.FieldUUID, field.TypeString, value) + _node.UUID = value + } + if nodes := upc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: userprofile.UserTable, + Columns: []string{userprofile.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.user_profile = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := upc.mutation.TextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := upc.mutation.UsertextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserProfileCreateBulk is the builder for creating many UserProfile entities in bulk. +type UserProfileCreateBulk struct { + config + builders []*UserProfileCreate +} + +// Save creates the UserProfile entities in the database. +func (upcb *UserProfileCreateBulk) Save(ctx context.Context) ([]*UserProfile, error) { + specs := make([]*sqlgraph.CreateSpec, len(upcb.builders)) + nodes := make([]*UserProfile, len(upcb.builders)) + mutators := make([]Mutator, len(upcb.builders)) + for i := range upcb.builders { + func(i int, root context.Context) { + builder := upcb.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserProfileMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, upcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, upcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, upcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (upcb *UserProfileCreateBulk) SaveX(ctx context.Context) []*UserProfile { + v, err := upcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (upcb *UserProfileCreateBulk) Exec(ctx context.Context) error { + _, err := upcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (upcb *UserProfileCreateBulk) ExecX(ctx context.Context) { + if err := upcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/userprofile_delete.go b/db/ent/userprofile_delete.go new file mode 100644 index 0000000..1575761 --- /dev/null +++ b/db/ent/userprofile_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" +) + +// UserProfileDelete is the builder for deleting a UserProfile entity. +type UserProfileDelete struct { + config + hooks []Hook + mutation *UserProfileMutation +} + +// Where appends a list predicates to the UserProfileDelete builder. +func (upd *UserProfileDelete) Where(ps ...predicate.UserProfile) *UserProfileDelete { + upd.mutation.Where(ps...) + return upd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (upd *UserProfileDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, upd.sqlExec, upd.mutation, upd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (upd *UserProfileDelete) ExecX(ctx context.Context) int { + n, err := upd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (upd *UserProfileDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(userprofile.Table, sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt)) + if ps := upd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, upd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + upd.mutation.done = true + return affected, err +} + +// UserProfileDeleteOne is the builder for deleting a single UserProfile entity. +type UserProfileDeleteOne struct { + upd *UserProfileDelete +} + +// Where appends a list predicates to the UserProfileDelete builder. +func (updo *UserProfileDeleteOne) Where(ps ...predicate.UserProfile) *UserProfileDeleteOne { + updo.upd.mutation.Where(ps...) + return updo +} + +// Exec executes the deletion query. +func (updo *UserProfileDeleteOne) Exec(ctx context.Context) error { + n, err := updo.upd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{userprofile.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (updo *UserProfileDeleteOne) ExecX(ctx context.Context) { + if err := updo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/userprofile_query.go b/db/ent/userprofile_query.go new file mode 100644 index 0000000..41ab268 --- /dev/null +++ b/db/ent/userprofile_query.go @@ -0,0 +1,850 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertexture" +) + +// UserProfileQuery is the builder for querying UserProfile entities. +type UserProfileQuery struct { + config + ctx *QueryContext + order []userprofile.OrderOption + inters []Interceptor + predicates []predicate.UserProfile + withUser *UserQuery + withTexture *TextureQuery + withUsertexture *UserTextureQuery + withFKs bool + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserProfileQuery builder. +func (upq *UserProfileQuery) Where(ps ...predicate.UserProfile) *UserProfileQuery { + upq.predicates = append(upq.predicates, ps...) + return upq +} + +// Limit the number of records to be returned by this query. +func (upq *UserProfileQuery) Limit(limit int) *UserProfileQuery { + upq.ctx.Limit = &limit + return upq +} + +// Offset to start from. +func (upq *UserProfileQuery) Offset(offset int) *UserProfileQuery { + upq.ctx.Offset = &offset + return upq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (upq *UserProfileQuery) Unique(unique bool) *UserProfileQuery { + upq.ctx.Unique = &unique + return upq +} + +// Order specifies how the records should be ordered. +func (upq *UserProfileQuery) Order(o ...userprofile.OrderOption) *UserProfileQuery { + upq.order = append(upq.order, o...) + return upq +} + +// QueryUser chains the current query on the "user" edge. +func (upq *UserProfileQuery) QueryUser() *UserQuery { + query := (&UserClient{config: upq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := upq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := upq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(userprofile.Table, userprofile.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, userprofile.UserTable, userprofile.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(upq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryTexture chains the current query on the "texture" edge. +func (upq *UserProfileQuery) QueryTexture() *TextureQuery { + query := (&TextureClient{config: upq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := upq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := upq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(userprofile.Table, userprofile.FieldID, selector), + sqlgraph.To(texture.Table, texture.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, userprofile.TextureTable, userprofile.TexturePrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(upq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryUsertexture chains the current query on the "usertexture" edge. +func (upq *UserProfileQuery) QueryUsertexture() *UserTextureQuery { + query := (&UserTextureClient{config: upq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := upq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := upq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(userprofile.Table, userprofile.FieldID, selector), + sqlgraph.To(usertexture.Table, usertexture.FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, userprofile.UsertextureTable, userprofile.UsertextureColumn), + ) + fromU = sqlgraph.SetNeighbors(upq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserProfile entity from the query. +// Returns a *NotFoundError when no UserProfile was found. +func (upq *UserProfileQuery) First(ctx context.Context) (*UserProfile, error) { + nodes, err := upq.Limit(1).All(setContextOp(ctx, upq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{userprofile.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (upq *UserProfileQuery) FirstX(ctx context.Context) *UserProfile { + node, err := upq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserProfile ID from the query. +// Returns a *NotFoundError when no UserProfile ID was found. +func (upq *UserProfileQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = upq.Limit(1).IDs(setContextOp(ctx, upq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{userprofile.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (upq *UserProfileQuery) FirstIDX(ctx context.Context) int { + id, err := upq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserProfile entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserProfile entity is found. +// Returns a *NotFoundError when no UserProfile entities are found. +func (upq *UserProfileQuery) Only(ctx context.Context) (*UserProfile, error) { + nodes, err := upq.Limit(2).All(setContextOp(ctx, upq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{userprofile.Label} + default: + return nil, &NotSingularError{userprofile.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (upq *UserProfileQuery) OnlyX(ctx context.Context) *UserProfile { + node, err := upq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserProfile ID in the query. +// Returns a *NotSingularError when more than one UserProfile ID is found. +// Returns a *NotFoundError when no entities are found. +func (upq *UserProfileQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = upq.Limit(2).IDs(setContextOp(ctx, upq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{userprofile.Label} + default: + err = &NotSingularError{userprofile.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (upq *UserProfileQuery) OnlyIDX(ctx context.Context) int { + id, err := upq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserProfiles. +func (upq *UserProfileQuery) All(ctx context.Context) ([]*UserProfile, error) { + ctx = setContextOp(ctx, upq.ctx, "All") + if err := upq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserProfile, *UserProfileQuery]() + return withInterceptors[[]*UserProfile](ctx, upq, qr, upq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (upq *UserProfileQuery) AllX(ctx context.Context) []*UserProfile { + nodes, err := upq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserProfile IDs. +func (upq *UserProfileQuery) IDs(ctx context.Context) (ids []int, err error) { + if upq.ctx.Unique == nil && upq.path != nil { + upq.Unique(true) + } + ctx = setContextOp(ctx, upq.ctx, "IDs") + if err = upq.Select(userprofile.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (upq *UserProfileQuery) IDsX(ctx context.Context) []int { + ids, err := upq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (upq *UserProfileQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, upq.ctx, "Count") + if err := upq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, upq, querierCount[*UserProfileQuery](), upq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (upq *UserProfileQuery) CountX(ctx context.Context) int { + count, err := upq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (upq *UserProfileQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, upq.ctx, "Exist") + switch _, err := upq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (upq *UserProfileQuery) ExistX(ctx context.Context) bool { + exist, err := upq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserProfileQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (upq *UserProfileQuery) Clone() *UserProfileQuery { + if upq == nil { + return nil + } + return &UserProfileQuery{ + config: upq.config, + ctx: upq.ctx.Clone(), + order: append([]userprofile.OrderOption{}, upq.order...), + inters: append([]Interceptor{}, upq.inters...), + predicates: append([]predicate.UserProfile{}, upq.predicates...), + withUser: upq.withUser.Clone(), + withTexture: upq.withTexture.Clone(), + withUsertexture: upq.withUsertexture.Clone(), + // clone intermediate query. + sql: upq.sql.Clone(), + path: upq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (upq *UserProfileQuery) WithUser(opts ...func(*UserQuery)) *UserProfileQuery { + query := (&UserClient{config: upq.config}).Query() + for _, opt := range opts { + opt(query) + } + upq.withUser = query + return upq +} + +// WithTexture tells the query-builder to eager-load the nodes that are connected to +// the "texture" edge. The optional arguments are used to configure the query builder of the edge. +func (upq *UserProfileQuery) WithTexture(opts ...func(*TextureQuery)) *UserProfileQuery { + query := (&TextureClient{config: upq.config}).Query() + for _, opt := range opts { + opt(query) + } + upq.withTexture = query + return upq +} + +// WithUsertexture tells the query-builder to eager-load the nodes that are connected to +// the "usertexture" edge. The optional arguments are used to configure the query builder of the edge. +func (upq *UserProfileQuery) WithUsertexture(opts ...func(*UserTextureQuery)) *UserProfileQuery { + query := (&UserTextureClient{config: upq.config}).Query() + for _, opt := range opts { + opt(query) + } + upq.withUsertexture = query + return upq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Name string `json:"name,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserProfile.Query(). +// GroupBy(userprofile.FieldName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (upq *UserProfileQuery) GroupBy(field string, fields ...string) *UserProfileGroupBy { + upq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserProfileGroupBy{build: upq} + grbuild.flds = &upq.ctx.Fields + grbuild.label = userprofile.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Name string `json:"name,omitempty"` +// } +// +// client.UserProfile.Query(). +// Select(userprofile.FieldName). +// Scan(ctx, &v) +func (upq *UserProfileQuery) Select(fields ...string) *UserProfileSelect { + upq.ctx.Fields = append(upq.ctx.Fields, fields...) + sbuild := &UserProfileSelect{UserProfileQuery: upq} + sbuild.label = userprofile.Label + sbuild.flds, sbuild.scan = &upq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserProfileSelect configured with the given aggregations. +func (upq *UserProfileQuery) Aggregate(fns ...AggregateFunc) *UserProfileSelect { + return upq.Select().Aggregate(fns...) +} + +func (upq *UserProfileQuery) prepareQuery(ctx context.Context) error { + for _, inter := range upq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, upq); err != nil { + return err + } + } + } + for _, f := range upq.ctx.Fields { + if !userprofile.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if upq.path != nil { + prev, err := upq.path(ctx) + if err != nil { + return err + } + upq.sql = prev + } + return nil +} + +func (upq *UserProfileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserProfile, error) { + var ( + nodes = []*UserProfile{} + withFKs = upq.withFKs + _spec = upq.querySpec() + loadedTypes = [3]bool{ + upq.withUser != nil, + upq.withTexture != nil, + upq.withUsertexture != nil, + } + ) + if upq.withUser != nil { + withFKs = true + } + if withFKs { + _spec.Node.Columns = append(_spec.Node.Columns, userprofile.ForeignKeys...) + } + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserProfile).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserProfile{config: upq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(upq.modifiers) > 0 { + _spec.Modifiers = upq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, upq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := upq.withUser; query != nil { + if err := upq.loadUser(ctx, query, nodes, nil, + func(n *UserProfile, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + if query := upq.withTexture; query != nil { + if err := upq.loadTexture(ctx, query, nodes, + func(n *UserProfile) { n.Edges.Texture = []*Texture{} }, + func(n *UserProfile, e *Texture) { n.Edges.Texture = append(n.Edges.Texture, e) }); err != nil { + return nil, err + } + } + if query := upq.withUsertexture; query != nil { + if err := upq.loadUsertexture(ctx, query, nodes, + func(n *UserProfile) { n.Edges.Usertexture = []*UserTexture{} }, + func(n *UserProfile, e *UserTexture) { n.Edges.Usertexture = append(n.Edges.Usertexture, e) }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (upq *UserProfileQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*UserProfile, init func(*UserProfile), assign func(*UserProfile, *User)) error { + ids := make([]int, 0, len(nodes)) + nodeids := make(map[int][]*UserProfile) + for i := range nodes { + if nodes[i].user_profile == nil { + continue + } + fk := *nodes[i].user_profile + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_profile" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (upq *UserProfileQuery) loadTexture(ctx context.Context, query *TextureQuery, nodes []*UserProfile, init func(*UserProfile), assign func(*UserProfile, *Texture)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[int]*UserProfile) + nids := make(map[int]map[*UserProfile]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(userprofile.TextureTable) + s.Join(joinT).On(s.C(texture.FieldID), joinT.C(userprofile.TexturePrimaryKey[0])) + s.Where(sql.InValues(joinT.C(userprofile.TexturePrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(userprofile.TexturePrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullInt64)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := int(values[0].(*sql.NullInt64).Int64) + inValue := int(values[1].(*sql.NullInt64).Int64) + if nids[inValue] == nil { + nids[inValue] = map[*UserProfile]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Texture](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "texture" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (upq *UserProfileQuery) loadUsertexture(ctx context.Context, query *UserTextureQuery, nodes []*UserProfile, init func(*UserProfile), assign func(*UserProfile, *UserTexture)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int]*UserProfile) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(usertexture.FieldUserProfileID) + } + query.Where(predicate.UserTexture(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(userprofile.UsertextureColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserProfileID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_profile_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (upq *UserProfileQuery) sqlCount(ctx context.Context) (int, error) { + _spec := upq.querySpec() + if len(upq.modifiers) > 0 { + _spec.Modifiers = upq.modifiers + } + _spec.Node.Columns = upq.ctx.Fields + if len(upq.ctx.Fields) > 0 { + _spec.Unique = upq.ctx.Unique != nil && *upq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, upq.driver, _spec) +} + +func (upq *UserProfileQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(userprofile.Table, userprofile.Columns, sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt)) + _spec.From = upq.sql + if unique := upq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if upq.path != nil { + _spec.Unique = true + } + if fields := upq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, userprofile.FieldID) + for i := range fields { + if fields[i] != userprofile.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := upq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := upq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := upq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := upq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (upq *UserProfileQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(upq.driver.Dialect()) + t1 := builder.Table(userprofile.Table) + columns := upq.ctx.Fields + if len(columns) == 0 { + columns = userprofile.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if upq.sql != nil { + selector = upq.sql + selector.Select(selector.Columns(columns...)...) + } + if upq.ctx.Unique != nil && *upq.ctx.Unique { + selector.Distinct() + } + for _, m := range upq.modifiers { + m(selector) + } + for _, p := range upq.predicates { + p(selector) + } + for _, p := range upq.order { + p(selector) + } + if offset := upq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := upq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (upq *UserProfileQuery) ForUpdate(opts ...sql.LockOption) *UserProfileQuery { + if upq.driver.Dialect() == dialect.Postgres { + upq.Unique(false) + } + upq.modifiers = append(upq.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return upq +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (upq *UserProfileQuery) ForShare(opts ...sql.LockOption) *UserProfileQuery { + if upq.driver.Dialect() == dialect.Postgres { + upq.Unique(false) + } + upq.modifiers = append(upq.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return upq +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (upq *UserProfileQuery) ForUpdateA(opts ...sql.LockOption) *UserProfileQuery { + if upq.driver.Dialect() == dialect.SQLite { + return upq + } + return upq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (upq *UserProfileQuery) ForShareA(opts ...sql.LockOption) *UserProfileQuery { + if upq.driver.Dialect() == dialect.SQLite { + return upq + } + return upq.ForShare(opts...) +} + +// UserProfileGroupBy is the group-by builder for UserProfile entities. +type UserProfileGroupBy struct { + selector + build *UserProfileQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (upgb *UserProfileGroupBy) Aggregate(fns ...AggregateFunc) *UserProfileGroupBy { + upgb.fns = append(upgb.fns, fns...) + return upgb +} + +// Scan applies the selector query and scans the result into the given value. +func (upgb *UserProfileGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, upgb.build.ctx, "GroupBy") + if err := upgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserProfileQuery, *UserProfileGroupBy](ctx, upgb.build, upgb, upgb.build.inters, v) +} + +func (upgb *UserProfileGroupBy) sqlScan(ctx context.Context, root *UserProfileQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(upgb.fns)) + for _, fn := range upgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*upgb.flds)+len(upgb.fns)) + for _, f := range *upgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*upgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := upgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserProfileSelect is the builder for selecting fields of UserProfile entities. +type UserProfileSelect struct { + *UserProfileQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ups *UserProfileSelect) Aggregate(fns ...AggregateFunc) *UserProfileSelect { + ups.fns = append(ups.fns, fns...) + return ups +} + +// Scan applies the selector query and scans the result into the given value. +func (ups *UserProfileSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ups.ctx, "Select") + if err := ups.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserProfileQuery, *UserProfileSelect](ctx, ups.UserProfileQuery, ups, ups.inters, v) +} + +func (ups *UserProfileSelect) sqlScan(ctx context.Context, root *UserProfileQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ups.fns)) + for _, fn := range ups.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ups.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ups.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/db/ent/userprofile_update.go b/db/ent/userprofile_update.go new file mode 100644 index 0000000..210775b --- /dev/null +++ b/db/ent/userprofile_update.go @@ -0,0 +1,652 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "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/db/ent/usertexture" +) + +// UserProfileUpdate is the builder for updating UserProfile entities. +type UserProfileUpdate struct { + config + hooks []Hook + mutation *UserProfileMutation +} + +// Where appends a list predicates to the UserProfileUpdate builder. +func (upu *UserProfileUpdate) Where(ps ...predicate.UserProfile) *UserProfileUpdate { + upu.mutation.Where(ps...) + return upu +} + +// SetName sets the "name" field. +func (upu *UserProfileUpdate) SetName(s string) *UserProfileUpdate { + upu.mutation.SetName(s) + return upu +} + +// SetUUID sets the "uuid" field. +func (upu *UserProfileUpdate) SetUUID(s string) *UserProfileUpdate { + upu.mutation.SetUUID(s) + return upu +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (upu *UserProfileUpdate) SetUserID(id int) *UserProfileUpdate { + upu.mutation.SetUserID(id) + return upu +} + +// SetUser sets the "user" edge to the User entity. +func (upu *UserProfileUpdate) SetUser(u *User) *UserProfileUpdate { + return upu.SetUserID(u.ID) +} + +// AddTextureIDs adds the "texture" edge to the Texture entity by IDs. +func (upu *UserProfileUpdate) AddTextureIDs(ids ...int) *UserProfileUpdate { + upu.mutation.AddTextureIDs(ids...) + return upu +} + +// AddTexture adds the "texture" edges to the Texture entity. +func (upu *UserProfileUpdate) AddTexture(t ...*Texture) *UserProfileUpdate { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return upu.AddTextureIDs(ids...) +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by IDs. +func (upu *UserProfileUpdate) AddUsertextureIDs(ids ...int) *UserProfileUpdate { + upu.mutation.AddUsertextureIDs(ids...) + return upu +} + +// AddUsertexture adds the "usertexture" edges to the UserTexture entity. +func (upu *UserProfileUpdate) AddUsertexture(u ...*UserTexture) *UserProfileUpdate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return upu.AddUsertextureIDs(ids...) +} + +// Mutation returns the UserProfileMutation object of the builder. +func (upu *UserProfileUpdate) Mutation() *UserProfileMutation { + return upu.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (upu *UserProfileUpdate) ClearUser() *UserProfileUpdate { + upu.mutation.ClearUser() + return upu +} + +// ClearTexture clears all "texture" edges to the Texture entity. +func (upu *UserProfileUpdate) ClearTexture() *UserProfileUpdate { + upu.mutation.ClearTexture() + return upu +} + +// RemoveTextureIDs removes the "texture" edge to Texture entities by IDs. +func (upu *UserProfileUpdate) RemoveTextureIDs(ids ...int) *UserProfileUpdate { + upu.mutation.RemoveTextureIDs(ids...) + return upu +} + +// RemoveTexture removes "texture" edges to Texture entities. +func (upu *UserProfileUpdate) RemoveTexture(t ...*Texture) *UserProfileUpdate { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return upu.RemoveTextureIDs(ids...) +} + +// ClearUsertexture clears all "usertexture" edges to the UserTexture entity. +func (upu *UserProfileUpdate) ClearUsertexture() *UserProfileUpdate { + upu.mutation.ClearUsertexture() + return upu +} + +// RemoveUsertextureIDs removes the "usertexture" edge to UserTexture entities by IDs. +func (upu *UserProfileUpdate) RemoveUsertextureIDs(ids ...int) *UserProfileUpdate { + upu.mutation.RemoveUsertextureIDs(ids...) + return upu +} + +// RemoveUsertexture removes "usertexture" edges to UserTexture entities. +func (upu *UserProfileUpdate) RemoveUsertexture(u ...*UserTexture) *UserProfileUpdate { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return upu.RemoveUsertextureIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (upu *UserProfileUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, upu.sqlSave, upu.mutation, upu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (upu *UserProfileUpdate) SaveX(ctx context.Context) int { + affected, err := upu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (upu *UserProfileUpdate) Exec(ctx context.Context) error { + _, err := upu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (upu *UserProfileUpdate) ExecX(ctx context.Context) { + if err := upu.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (upu *UserProfileUpdate) check() error { + if _, ok := upu.mutation.UserID(); upu.mutation.UserCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "UserProfile.user"`) + } + return nil +} + +func (upu *UserProfileUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := upu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(userprofile.Table, userprofile.Columns, sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt)) + if ps := upu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := upu.mutation.Name(); ok { + _spec.SetField(userprofile.FieldName, field.TypeString, value) + } + if value, ok := upu.mutation.UUID(); ok { + _spec.SetField(userprofile.FieldUUID, field.TypeString, value) + } + if upu.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: userprofile.UserTable, + Columns: []string{userprofile.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upu.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: userprofile.UserTable, + Columns: []string{userprofile.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if upu.mutation.TextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upu.mutation.RemovedTextureIDs(); len(nodes) > 0 && !upu.mutation.TextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upu.mutation.TextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if upu.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upu.mutation.RemovedUsertextureIDs(); len(nodes) > 0 && !upu.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upu.mutation.UsertextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, upu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{userprofile.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + upu.mutation.done = true + return n, nil +} + +// UserProfileUpdateOne is the builder for updating a single UserProfile entity. +type UserProfileUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserProfileMutation +} + +// SetName sets the "name" field. +func (upuo *UserProfileUpdateOne) SetName(s string) *UserProfileUpdateOne { + upuo.mutation.SetName(s) + return upuo +} + +// SetUUID sets the "uuid" field. +func (upuo *UserProfileUpdateOne) SetUUID(s string) *UserProfileUpdateOne { + upuo.mutation.SetUUID(s) + return upuo +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (upuo *UserProfileUpdateOne) SetUserID(id int) *UserProfileUpdateOne { + upuo.mutation.SetUserID(id) + return upuo +} + +// SetUser sets the "user" edge to the User entity. +func (upuo *UserProfileUpdateOne) SetUser(u *User) *UserProfileUpdateOne { + return upuo.SetUserID(u.ID) +} + +// AddTextureIDs adds the "texture" edge to the Texture entity by IDs. +func (upuo *UserProfileUpdateOne) AddTextureIDs(ids ...int) *UserProfileUpdateOne { + upuo.mutation.AddTextureIDs(ids...) + return upuo +} + +// AddTexture adds the "texture" edges to the Texture entity. +func (upuo *UserProfileUpdateOne) AddTexture(t ...*Texture) *UserProfileUpdateOne { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return upuo.AddTextureIDs(ids...) +} + +// AddUsertextureIDs adds the "usertexture" edge to the UserTexture entity by IDs. +func (upuo *UserProfileUpdateOne) AddUsertextureIDs(ids ...int) *UserProfileUpdateOne { + upuo.mutation.AddUsertextureIDs(ids...) + return upuo +} + +// AddUsertexture adds the "usertexture" edges to the UserTexture entity. +func (upuo *UserProfileUpdateOne) AddUsertexture(u ...*UserTexture) *UserProfileUpdateOne { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return upuo.AddUsertextureIDs(ids...) +} + +// Mutation returns the UserProfileMutation object of the builder. +func (upuo *UserProfileUpdateOne) Mutation() *UserProfileMutation { + return upuo.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (upuo *UserProfileUpdateOne) ClearUser() *UserProfileUpdateOne { + upuo.mutation.ClearUser() + return upuo +} + +// ClearTexture clears all "texture" edges to the Texture entity. +func (upuo *UserProfileUpdateOne) ClearTexture() *UserProfileUpdateOne { + upuo.mutation.ClearTexture() + return upuo +} + +// RemoveTextureIDs removes the "texture" edge to Texture entities by IDs. +func (upuo *UserProfileUpdateOne) RemoveTextureIDs(ids ...int) *UserProfileUpdateOne { + upuo.mutation.RemoveTextureIDs(ids...) + return upuo +} + +// RemoveTexture removes "texture" edges to Texture entities. +func (upuo *UserProfileUpdateOne) RemoveTexture(t ...*Texture) *UserProfileUpdateOne { + ids := make([]int, len(t)) + for i := range t { + ids[i] = t[i].ID + } + return upuo.RemoveTextureIDs(ids...) +} + +// ClearUsertexture clears all "usertexture" edges to the UserTexture entity. +func (upuo *UserProfileUpdateOne) ClearUsertexture() *UserProfileUpdateOne { + upuo.mutation.ClearUsertexture() + return upuo +} + +// RemoveUsertextureIDs removes the "usertexture" edge to UserTexture entities by IDs. +func (upuo *UserProfileUpdateOne) RemoveUsertextureIDs(ids ...int) *UserProfileUpdateOne { + upuo.mutation.RemoveUsertextureIDs(ids...) + return upuo +} + +// RemoveUsertexture removes "usertexture" edges to UserTexture entities. +func (upuo *UserProfileUpdateOne) RemoveUsertexture(u ...*UserTexture) *UserProfileUpdateOne { + ids := make([]int, len(u)) + for i := range u { + ids[i] = u[i].ID + } + return upuo.RemoveUsertextureIDs(ids...) +} + +// Where appends a list predicates to the UserProfileUpdate builder. +func (upuo *UserProfileUpdateOne) Where(ps ...predicate.UserProfile) *UserProfileUpdateOne { + upuo.mutation.Where(ps...) + return upuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (upuo *UserProfileUpdateOne) Select(field string, fields ...string) *UserProfileUpdateOne { + upuo.fields = append([]string{field}, fields...) + return upuo +} + +// Save executes the query and returns the updated UserProfile entity. +func (upuo *UserProfileUpdateOne) Save(ctx context.Context) (*UserProfile, error) { + return withHooks(ctx, upuo.sqlSave, upuo.mutation, upuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (upuo *UserProfileUpdateOne) SaveX(ctx context.Context) *UserProfile { + node, err := upuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (upuo *UserProfileUpdateOne) Exec(ctx context.Context) error { + _, err := upuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (upuo *UserProfileUpdateOne) ExecX(ctx context.Context) { + if err := upuo.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (upuo *UserProfileUpdateOne) check() error { + if _, ok := upuo.mutation.UserID(); upuo.mutation.UserCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "UserProfile.user"`) + } + return nil +} + +func (upuo *UserProfileUpdateOne) sqlSave(ctx context.Context) (_node *UserProfile, err error) { + if err := upuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(userprofile.Table, userprofile.Columns, sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt)) + id, ok := upuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "UserProfile.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := upuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, userprofile.FieldID) + for _, f := range fields { + if !userprofile.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != userprofile.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := upuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := upuo.mutation.Name(); ok { + _spec.SetField(userprofile.FieldName, field.TypeString, value) + } + if value, ok := upuo.mutation.UUID(); ok { + _spec.SetField(userprofile.FieldUUID, field.TypeString, value) + } + if upuo.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: userprofile.UserTable, + Columns: []string{userprofile.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upuo.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: userprofile.UserTable, + Columns: []string{userprofile.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if upuo.mutation.TextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upuo.mutation.RemovedTextureIDs(); len(nodes) > 0 && !upuo.mutation.TextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upuo.mutation.TextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: userprofile.TextureTable, + Columns: userprofile.TexturePrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if upuo.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upuo.mutation.RemovedUsertextureIDs(); len(nodes) > 0 && !upuo.mutation.UsertextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := upuo.mutation.UsertextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: true, + Table: userprofile.UsertextureTable, + Columns: []string{userprofile.UsertextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &UserProfile{config: upuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, upuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{userprofile.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + upuo.mutation.done = true + return _node, nil +} diff --git a/db/ent/usertexture.go b/db/ent/usertexture.go new file mode 100644 index 0000000..d792079 --- /dev/null +++ b/db/ent/usertexture.go @@ -0,0 +1,188 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/xmdhs/authlib-skin/db/ent/texture" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/db/ent/usertexture" +) + +// UserTexture is the model entity for the UserTexture schema. +type UserTexture struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // UserProfileID holds the value of the "user_profile_id" field. + UserProfileID int `json:"user_profile_id,omitempty"` + // TextureID holds the value of the "texture_id" field. + TextureID int `json:"texture_id,omitempty"` + // Type holds the value of the "type" field. + Type string `json:"type,omitempty"` + // Variant holds the value of the "variant" field. + Variant string `json:"variant,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserTextureQuery when eager-loading is set. + Edges UserTextureEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserTextureEdges holds the relations/edges for other nodes in the graph. +type UserTextureEdges struct { + // UserProfile holds the value of the user_profile edge. + UserProfile *UserProfile `json:"user_profile,omitempty"` + // Texture holds the value of the texture edge. + Texture *Texture `json:"texture,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// UserProfileOrErr returns the UserProfile value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserTextureEdges) UserProfileOrErr() (*UserProfile, error) { + if e.loadedTypes[0] { + if e.UserProfile == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: userprofile.Label} + } + return e.UserProfile, nil + } + return nil, &NotLoadedError{edge: "user_profile"} +} + +// TextureOrErr returns the Texture value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserTextureEdges) TextureOrErr() (*Texture, error) { + if e.loadedTypes[1] { + if e.Texture == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: texture.Label} + } + return e.Texture, nil + } + return nil, &NotLoadedError{edge: "texture"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserTexture) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case usertexture.FieldID, usertexture.FieldUserProfileID, usertexture.FieldTextureID: + values[i] = new(sql.NullInt64) + case usertexture.FieldType, usertexture.FieldVariant: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserTexture fields. +func (ut *UserTexture) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case usertexture.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + ut.ID = int(value.Int64) + case usertexture.FieldUserProfileID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field user_profile_id", values[i]) + } else if value.Valid { + ut.UserProfileID = int(value.Int64) + } + case usertexture.FieldTextureID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field texture_id", values[i]) + } else if value.Valid { + ut.TextureID = int(value.Int64) + } + case usertexture.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + ut.Type = value.String + } + case usertexture.FieldVariant: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field variant", values[i]) + } else if value.Valid { + ut.Variant = value.String + } + default: + ut.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserTexture. +// This includes values selected through modifiers, order, etc. +func (ut *UserTexture) Value(name string) (ent.Value, error) { + return ut.selectValues.Get(name) +} + +// QueryUserProfile queries the "user_profile" edge of the UserTexture entity. +func (ut *UserTexture) QueryUserProfile() *UserProfileQuery { + return NewUserTextureClient(ut.config).QueryUserProfile(ut) +} + +// QueryTexture queries the "texture" edge of the UserTexture entity. +func (ut *UserTexture) QueryTexture() *TextureQuery { + return NewUserTextureClient(ut.config).QueryTexture(ut) +} + +// Update returns a builder for updating this UserTexture. +// Note that you need to call UserTexture.Unwrap() before calling this method if this UserTexture +// was returned from a transaction, and the transaction was committed or rolled back. +func (ut *UserTexture) Update() *UserTextureUpdateOne { + return NewUserTextureClient(ut.config).UpdateOne(ut) +} + +// Unwrap unwraps the UserTexture entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (ut *UserTexture) Unwrap() *UserTexture { + _tx, ok := ut.config.driver.(*txDriver) + if !ok { + panic("ent: UserTexture is not a transactional entity") + } + ut.config.driver = _tx.drv + return ut +} + +// String implements the fmt.Stringer. +func (ut *UserTexture) String() string { + var builder strings.Builder + builder.WriteString("UserTexture(") + builder.WriteString(fmt.Sprintf("id=%v, ", ut.ID)) + builder.WriteString("user_profile_id=") + builder.WriteString(fmt.Sprintf("%v", ut.UserProfileID)) + builder.WriteString(", ") + builder.WriteString("texture_id=") + builder.WriteString(fmt.Sprintf("%v", ut.TextureID)) + builder.WriteString(", ") + builder.WriteString("type=") + builder.WriteString(ut.Type) + builder.WriteString(", ") + builder.WriteString("variant=") + builder.WriteString(ut.Variant) + builder.WriteByte(')') + return builder.String() +} + +// UserTextures is a parsable slice of UserTexture. +type UserTextures []*UserTexture diff --git a/db/ent/usertexture/usertexture.go b/db/ent/usertexture/usertexture.go new file mode 100644 index 0000000..6c8c797 --- /dev/null +++ b/db/ent/usertexture/usertexture.go @@ -0,0 +1,118 @@ +// Code generated by ent, DO NOT EDIT. + +package usertexture + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the usertexture type in the database. + Label = "user_texture" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldUserProfileID holds the string denoting the user_profile_id field in the database. + FieldUserProfileID = "user_profile_id" + // FieldTextureID holds the string denoting the texture_id field in the database. + FieldTextureID = "texture_id" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldVariant holds the string denoting the variant field in the database. + FieldVariant = "variant" + // EdgeUserProfile holds the string denoting the user_profile edge name in mutations. + EdgeUserProfile = "user_profile" + // EdgeTexture holds the string denoting the texture edge name in mutations. + EdgeTexture = "texture" + // Table holds the table name of the usertexture in the database. + Table = "user_textures" + // UserProfileTable is the table that holds the user_profile relation/edge. + UserProfileTable = "user_textures" + // UserProfileInverseTable is the table name for the UserProfile entity. + // It exists in this package in order to avoid circular dependency with the "userprofile" package. + UserProfileInverseTable = "user_profiles" + // UserProfileColumn is the table column denoting the user_profile relation/edge. + UserProfileColumn = "user_profile_id" + // TextureTable is the table that holds the texture relation/edge. + TextureTable = "user_textures" + // TextureInverseTable is the table name for the Texture entity. + // It exists in this package in order to avoid circular dependency with the "texture" package. + TextureInverseTable = "textures" + // TextureColumn is the table column denoting the texture relation/edge. + TextureColumn = "texture_id" +) + +// Columns holds all SQL columns for usertexture fields. +var Columns = []string{ + FieldID, + FieldUserProfileID, + FieldTextureID, + FieldType, + FieldVariant, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +// OrderOption defines the ordering options for the UserTexture queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByUserProfileID orders the results by the user_profile_id field. +func ByUserProfileID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserProfileID, opts...).ToFunc() +} + +// ByTextureID orders the results by the texture_id field. +func ByTextureID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTextureID, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByVariant orders the results by the variant field. +func ByVariant(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldVariant, opts...).ToFunc() +} + +// ByUserProfileField orders the results by user_profile field. +func ByUserProfileField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserProfileStep(), sql.OrderByField(field, opts...)) + } +} + +// ByTextureField orders the results by texture field. +func ByTextureField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTextureStep(), sql.OrderByField(field, opts...)) + } +} +func newUserProfileStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserProfileInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, UserProfileTable, UserProfileColumn), + ) +} +func newTextureStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TextureInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, TextureTable, TextureColumn), + ) +} diff --git a/db/ent/usertexture/where.go b/db/ent/usertexture/where.go new file mode 100644 index 0000000..19ff4cf --- /dev/null +++ b/db/ent/usertexture/where.go @@ -0,0 +1,322 @@ +// Code generated by ent, DO NOT EDIT. + +package usertexture + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/xmdhs/authlib-skin/db/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldLTE(FieldID, id)) +} + +// UserProfileID applies equality check predicate on the "user_profile_id" field. It's identical to UserProfileIDEQ. +func UserProfileID(v int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldUserProfileID, v)) +} + +// TextureID applies equality check predicate on the "texture_id" field. It's identical to TextureIDEQ. +func TextureID(v int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldTextureID, v)) +} + +// Type applies equality check predicate on the "type" field. It's identical to TypeEQ. +func Type(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldType, v)) +} + +// Variant applies equality check predicate on the "variant" field. It's identical to VariantEQ. +func Variant(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldVariant, v)) +} + +// UserProfileIDEQ applies the EQ predicate on the "user_profile_id" field. +func UserProfileIDEQ(v int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldUserProfileID, v)) +} + +// UserProfileIDNEQ applies the NEQ predicate on the "user_profile_id" field. +func UserProfileIDNEQ(v int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNEQ(FieldUserProfileID, v)) +} + +// UserProfileIDIn applies the In predicate on the "user_profile_id" field. +func UserProfileIDIn(vs ...int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldIn(FieldUserProfileID, vs...)) +} + +// UserProfileIDNotIn applies the NotIn predicate on the "user_profile_id" field. +func UserProfileIDNotIn(vs ...int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNotIn(FieldUserProfileID, vs...)) +} + +// TextureIDEQ applies the EQ predicate on the "texture_id" field. +func TextureIDEQ(v int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldTextureID, v)) +} + +// TextureIDNEQ applies the NEQ predicate on the "texture_id" field. +func TextureIDNEQ(v int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNEQ(FieldTextureID, v)) +} + +// TextureIDIn applies the In predicate on the "texture_id" field. +func TextureIDIn(vs ...int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldIn(FieldTextureID, vs...)) +} + +// TextureIDNotIn applies the NotIn predicate on the "texture_id" field. +func TextureIDNotIn(vs ...int) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNotIn(FieldTextureID, vs...)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldType, v)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNEQ(FieldType, v)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldIn(FieldType, vs...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNotIn(FieldType, vs...)) +} + +// TypeGT applies the GT predicate on the "type" field. +func TypeGT(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldGT(FieldType, v)) +} + +// TypeGTE applies the GTE predicate on the "type" field. +func TypeGTE(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldGTE(FieldType, v)) +} + +// TypeLT applies the LT predicate on the "type" field. +func TypeLT(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldLT(FieldType, v)) +} + +// TypeLTE applies the LTE predicate on the "type" field. +func TypeLTE(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldLTE(FieldType, v)) +} + +// TypeContains applies the Contains predicate on the "type" field. +func TypeContains(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldContains(FieldType, v)) +} + +// TypeHasPrefix applies the HasPrefix predicate on the "type" field. +func TypeHasPrefix(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldHasPrefix(FieldType, v)) +} + +// TypeHasSuffix applies the HasSuffix predicate on the "type" field. +func TypeHasSuffix(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldHasSuffix(FieldType, v)) +} + +// TypeEqualFold applies the EqualFold predicate on the "type" field. +func TypeEqualFold(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEqualFold(FieldType, v)) +} + +// TypeContainsFold applies the ContainsFold predicate on the "type" field. +func TypeContainsFold(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldContainsFold(FieldType, v)) +} + +// VariantEQ applies the EQ predicate on the "variant" field. +func VariantEQ(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEQ(FieldVariant, v)) +} + +// VariantNEQ applies the NEQ predicate on the "variant" field. +func VariantNEQ(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNEQ(FieldVariant, v)) +} + +// VariantIn applies the In predicate on the "variant" field. +func VariantIn(vs ...string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldIn(FieldVariant, vs...)) +} + +// VariantNotIn applies the NotIn predicate on the "variant" field. +func VariantNotIn(vs ...string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldNotIn(FieldVariant, vs...)) +} + +// VariantGT applies the GT predicate on the "variant" field. +func VariantGT(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldGT(FieldVariant, v)) +} + +// VariantGTE applies the GTE predicate on the "variant" field. +func VariantGTE(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldGTE(FieldVariant, v)) +} + +// VariantLT applies the LT predicate on the "variant" field. +func VariantLT(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldLT(FieldVariant, v)) +} + +// VariantLTE applies the LTE predicate on the "variant" field. +func VariantLTE(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldLTE(FieldVariant, v)) +} + +// VariantContains applies the Contains predicate on the "variant" field. +func VariantContains(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldContains(FieldVariant, v)) +} + +// VariantHasPrefix applies the HasPrefix predicate on the "variant" field. +func VariantHasPrefix(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldHasPrefix(FieldVariant, v)) +} + +// VariantHasSuffix applies the HasSuffix predicate on the "variant" field. +func VariantHasSuffix(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldHasSuffix(FieldVariant, v)) +} + +// VariantEqualFold applies the EqualFold predicate on the "variant" field. +func VariantEqualFold(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldEqualFold(FieldVariant, v)) +} + +// VariantContainsFold applies the ContainsFold predicate on the "variant" field. +func VariantContainsFold(v string) predicate.UserTexture { + return predicate.UserTexture(sql.FieldContainsFold(FieldVariant, v)) +} + +// HasUserProfile applies the HasEdge predicate on the "user_profile" edge. +func HasUserProfile() predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, UserProfileTable, UserProfileColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserProfileWith applies the HasEdge predicate on the "user_profile" edge with a given conditions (other predicates). +func HasUserProfileWith(preds ...predicate.UserProfile) predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + step := newUserProfileStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasTexture applies the HasEdge predicate on the "texture" edge. +func HasTexture() predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, TextureTable, TextureColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTextureWith applies the HasEdge predicate on the "texture" edge with a given conditions (other predicates). +func HasTextureWith(preds ...predicate.Texture) predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + step := newTextureStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserTexture) predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserTexture) predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserTexture) predicate.UserTexture { + return predicate.UserTexture(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/db/ent/usertexture_create.go b/db/ent/usertexture_create.go new file mode 100644 index 0000000..accead8 --- /dev/null +++ b/db/ent/usertexture_create.go @@ -0,0 +1,262 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/texture" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/db/ent/usertexture" +) + +// UserTextureCreate is the builder for creating a UserTexture entity. +type UserTextureCreate struct { + config + mutation *UserTextureMutation + hooks []Hook +} + +// SetUserProfileID sets the "user_profile_id" field. +func (utc *UserTextureCreate) SetUserProfileID(i int) *UserTextureCreate { + utc.mutation.SetUserProfileID(i) + return utc +} + +// SetTextureID sets the "texture_id" field. +func (utc *UserTextureCreate) SetTextureID(i int) *UserTextureCreate { + utc.mutation.SetTextureID(i) + return utc +} + +// SetType sets the "type" field. +func (utc *UserTextureCreate) SetType(s string) *UserTextureCreate { + utc.mutation.SetType(s) + return utc +} + +// SetVariant sets the "variant" field. +func (utc *UserTextureCreate) SetVariant(s string) *UserTextureCreate { + utc.mutation.SetVariant(s) + return utc +} + +// SetUserProfile sets the "user_profile" edge to the UserProfile entity. +func (utc *UserTextureCreate) SetUserProfile(u *UserProfile) *UserTextureCreate { + return utc.SetUserProfileID(u.ID) +} + +// SetTexture sets the "texture" edge to the Texture entity. +func (utc *UserTextureCreate) SetTexture(t *Texture) *UserTextureCreate { + return utc.SetTextureID(t.ID) +} + +// Mutation returns the UserTextureMutation object of the builder. +func (utc *UserTextureCreate) Mutation() *UserTextureMutation { + return utc.mutation +} + +// Save creates the UserTexture in the database. +func (utc *UserTextureCreate) Save(ctx context.Context) (*UserTexture, error) { + return withHooks(ctx, utc.sqlSave, utc.mutation, utc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (utc *UserTextureCreate) SaveX(ctx context.Context) *UserTexture { + v, err := utc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (utc *UserTextureCreate) Exec(ctx context.Context) error { + _, err := utc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utc *UserTextureCreate) ExecX(ctx context.Context) { + if err := utc.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (utc *UserTextureCreate) check() error { + if _, ok := utc.mutation.UserProfileID(); !ok { + return &ValidationError{Name: "user_profile_id", err: errors.New(`ent: missing required field "UserTexture.user_profile_id"`)} + } + if _, ok := utc.mutation.TextureID(); !ok { + return &ValidationError{Name: "texture_id", err: errors.New(`ent: missing required field "UserTexture.texture_id"`)} + } + if _, ok := utc.mutation.GetType(); !ok { + return &ValidationError{Name: "type", err: errors.New(`ent: missing required field "UserTexture.type"`)} + } + if _, ok := utc.mutation.Variant(); !ok { + return &ValidationError{Name: "variant", err: errors.New(`ent: missing required field "UserTexture.variant"`)} + } + if _, ok := utc.mutation.UserProfileID(); !ok { + return &ValidationError{Name: "user_profile", err: errors.New(`ent: missing required edge "UserTexture.user_profile"`)} + } + if _, ok := utc.mutation.TextureID(); !ok { + return &ValidationError{Name: "texture", err: errors.New(`ent: missing required edge "UserTexture.texture"`)} + } + return nil +} + +func (utc *UserTextureCreate) sqlSave(ctx context.Context) (*UserTexture, error) { + if err := utc.check(); err != nil { + return nil, err + } + _node, _spec := utc.createSpec() + if err := sqlgraph.CreateNode(ctx, utc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + utc.mutation.id = &_node.ID + utc.mutation.done = true + return _node, nil +} + +func (utc *UserTextureCreate) createSpec() (*UserTexture, *sqlgraph.CreateSpec) { + var ( + _node = &UserTexture{config: utc.config} + _spec = sqlgraph.NewCreateSpec(usertexture.Table, sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt)) + ) + if value, ok := utc.mutation.GetType(); ok { + _spec.SetField(usertexture.FieldType, field.TypeString, value) + _node.Type = value + } + if value, ok := utc.mutation.Variant(); ok { + _spec.SetField(usertexture.FieldVariant, field.TypeString, value) + _node.Variant = value + } + if nodes := utc.mutation.UserProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.UserProfileTable, + Columns: []string{usertexture.UserProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserProfileID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := utc.mutation.TextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.TextureTable, + Columns: []string{usertexture.TextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.TextureID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserTextureCreateBulk is the builder for creating many UserTexture entities in bulk. +type UserTextureCreateBulk struct { + config + builders []*UserTextureCreate +} + +// Save creates the UserTexture entities in the database. +func (utcb *UserTextureCreateBulk) Save(ctx context.Context) ([]*UserTexture, error) { + specs := make([]*sqlgraph.CreateSpec, len(utcb.builders)) + nodes := make([]*UserTexture, len(utcb.builders)) + mutators := make([]Mutator, len(utcb.builders)) + for i := range utcb.builders { + func(i int, root context.Context) { + builder := utcb.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserTextureMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, utcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, utcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, utcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (utcb *UserTextureCreateBulk) SaveX(ctx context.Context) []*UserTexture { + v, err := utcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (utcb *UserTextureCreateBulk) Exec(ctx context.Context) error { + _, err := utcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utcb *UserTextureCreateBulk) ExecX(ctx context.Context) { + if err := utcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/usertexture_delete.go b/db/ent/usertexture_delete.go new file mode 100644 index 0000000..62b7bb0 --- /dev/null +++ b/db/ent/usertexture_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/usertexture" +) + +// UserTextureDelete is the builder for deleting a UserTexture entity. +type UserTextureDelete struct { + config + hooks []Hook + mutation *UserTextureMutation +} + +// Where appends a list predicates to the UserTextureDelete builder. +func (utd *UserTextureDelete) Where(ps ...predicate.UserTexture) *UserTextureDelete { + utd.mutation.Where(ps...) + return utd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (utd *UserTextureDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, utd.sqlExec, utd.mutation, utd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (utd *UserTextureDelete) ExecX(ctx context.Context) int { + n, err := utd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (utd *UserTextureDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(usertexture.Table, sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt)) + if ps := utd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, utd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + utd.mutation.done = true + return affected, err +} + +// UserTextureDeleteOne is the builder for deleting a single UserTexture entity. +type UserTextureDeleteOne struct { + utd *UserTextureDelete +} + +// Where appends a list predicates to the UserTextureDelete builder. +func (utdo *UserTextureDeleteOne) Where(ps ...predicate.UserTexture) *UserTextureDeleteOne { + utdo.utd.mutation.Where(ps...) + return utdo +} + +// Exec executes the deletion query. +func (utdo *UserTextureDeleteOne) Exec(ctx context.Context) error { + n, err := utdo.utd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{usertexture.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (utdo *UserTextureDeleteOne) ExecX(ctx context.Context) { + if err := utdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/usertexture_query.go b/db/ent/usertexture_query.go new file mode 100644 index 0000000..4ce98f9 --- /dev/null +++ b/db/ent/usertexture_query.go @@ -0,0 +1,737 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/texture" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/db/ent/usertexture" +) + +// UserTextureQuery is the builder for querying UserTexture entities. +type UserTextureQuery struct { + config + ctx *QueryContext + order []usertexture.OrderOption + inters []Interceptor + predicates []predicate.UserTexture + withUserProfile *UserProfileQuery + withTexture *TextureQuery + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserTextureQuery builder. +func (utq *UserTextureQuery) Where(ps ...predicate.UserTexture) *UserTextureQuery { + utq.predicates = append(utq.predicates, ps...) + return utq +} + +// Limit the number of records to be returned by this query. +func (utq *UserTextureQuery) Limit(limit int) *UserTextureQuery { + utq.ctx.Limit = &limit + return utq +} + +// Offset to start from. +func (utq *UserTextureQuery) Offset(offset int) *UserTextureQuery { + utq.ctx.Offset = &offset + return utq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (utq *UserTextureQuery) Unique(unique bool) *UserTextureQuery { + utq.ctx.Unique = &unique + return utq +} + +// Order specifies how the records should be ordered. +func (utq *UserTextureQuery) Order(o ...usertexture.OrderOption) *UserTextureQuery { + utq.order = append(utq.order, o...) + return utq +} + +// QueryUserProfile chains the current query on the "user_profile" edge. +func (utq *UserTextureQuery) QueryUserProfile() *UserProfileQuery { + query := (&UserProfileClient{config: utq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := utq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := utq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usertexture.Table, usertexture.FieldID, selector), + sqlgraph.To(userprofile.Table, userprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, usertexture.UserProfileTable, usertexture.UserProfileColumn), + ) + fromU = sqlgraph.SetNeighbors(utq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryTexture chains the current query on the "texture" edge. +func (utq *UserTextureQuery) QueryTexture() *TextureQuery { + query := (&TextureClient{config: utq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := utq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := utq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usertexture.Table, usertexture.FieldID, selector), + sqlgraph.To(texture.Table, texture.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, usertexture.TextureTable, usertexture.TextureColumn), + ) + fromU = sqlgraph.SetNeighbors(utq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserTexture entity from the query. +// Returns a *NotFoundError when no UserTexture was found. +func (utq *UserTextureQuery) First(ctx context.Context) (*UserTexture, error) { + nodes, err := utq.Limit(1).All(setContextOp(ctx, utq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{usertexture.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (utq *UserTextureQuery) FirstX(ctx context.Context) *UserTexture { + node, err := utq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserTexture ID from the query. +// Returns a *NotFoundError when no UserTexture ID was found. +func (utq *UserTextureQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = utq.Limit(1).IDs(setContextOp(ctx, utq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{usertexture.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (utq *UserTextureQuery) FirstIDX(ctx context.Context) int { + id, err := utq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserTexture entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserTexture entity is found. +// Returns a *NotFoundError when no UserTexture entities are found. +func (utq *UserTextureQuery) Only(ctx context.Context) (*UserTexture, error) { + nodes, err := utq.Limit(2).All(setContextOp(ctx, utq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{usertexture.Label} + default: + return nil, &NotSingularError{usertexture.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (utq *UserTextureQuery) OnlyX(ctx context.Context) *UserTexture { + node, err := utq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserTexture ID in the query. +// Returns a *NotSingularError when more than one UserTexture ID is found. +// Returns a *NotFoundError when no entities are found. +func (utq *UserTextureQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = utq.Limit(2).IDs(setContextOp(ctx, utq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{usertexture.Label} + default: + err = &NotSingularError{usertexture.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (utq *UserTextureQuery) OnlyIDX(ctx context.Context) int { + id, err := utq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserTextures. +func (utq *UserTextureQuery) All(ctx context.Context) ([]*UserTexture, error) { + ctx = setContextOp(ctx, utq.ctx, "All") + if err := utq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserTexture, *UserTextureQuery]() + return withInterceptors[[]*UserTexture](ctx, utq, qr, utq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (utq *UserTextureQuery) AllX(ctx context.Context) []*UserTexture { + nodes, err := utq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserTexture IDs. +func (utq *UserTextureQuery) IDs(ctx context.Context) (ids []int, err error) { + if utq.ctx.Unique == nil && utq.path != nil { + utq.Unique(true) + } + ctx = setContextOp(ctx, utq.ctx, "IDs") + if err = utq.Select(usertexture.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (utq *UserTextureQuery) IDsX(ctx context.Context) []int { + ids, err := utq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (utq *UserTextureQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, utq.ctx, "Count") + if err := utq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, utq, querierCount[*UserTextureQuery](), utq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (utq *UserTextureQuery) CountX(ctx context.Context) int { + count, err := utq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (utq *UserTextureQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, utq.ctx, "Exist") + switch _, err := utq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (utq *UserTextureQuery) ExistX(ctx context.Context) bool { + exist, err := utq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserTextureQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (utq *UserTextureQuery) Clone() *UserTextureQuery { + if utq == nil { + return nil + } + return &UserTextureQuery{ + config: utq.config, + ctx: utq.ctx.Clone(), + order: append([]usertexture.OrderOption{}, utq.order...), + inters: append([]Interceptor{}, utq.inters...), + predicates: append([]predicate.UserTexture{}, utq.predicates...), + withUserProfile: utq.withUserProfile.Clone(), + withTexture: utq.withTexture.Clone(), + // clone intermediate query. + sql: utq.sql.Clone(), + path: utq.path, + } +} + +// WithUserProfile tells the query-builder to eager-load the nodes that are connected to +// the "user_profile" edge. The optional arguments are used to configure the query builder of the edge. +func (utq *UserTextureQuery) WithUserProfile(opts ...func(*UserProfileQuery)) *UserTextureQuery { + query := (&UserProfileClient{config: utq.config}).Query() + for _, opt := range opts { + opt(query) + } + utq.withUserProfile = query + return utq +} + +// WithTexture tells the query-builder to eager-load the nodes that are connected to +// the "texture" edge. The optional arguments are used to configure the query builder of the edge. +func (utq *UserTextureQuery) WithTexture(opts ...func(*TextureQuery)) *UserTextureQuery { + query := (&TextureClient{config: utq.config}).Query() + for _, opt := range opts { + opt(query) + } + utq.withTexture = query + return utq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// UserProfileID int `json:"user_profile_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserTexture.Query(). +// GroupBy(usertexture.FieldUserProfileID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (utq *UserTextureQuery) GroupBy(field string, fields ...string) *UserTextureGroupBy { + utq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserTextureGroupBy{build: utq} + grbuild.flds = &utq.ctx.Fields + grbuild.label = usertexture.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// UserProfileID int `json:"user_profile_id,omitempty"` +// } +// +// client.UserTexture.Query(). +// Select(usertexture.FieldUserProfileID). +// Scan(ctx, &v) +func (utq *UserTextureQuery) Select(fields ...string) *UserTextureSelect { + utq.ctx.Fields = append(utq.ctx.Fields, fields...) + sbuild := &UserTextureSelect{UserTextureQuery: utq} + sbuild.label = usertexture.Label + sbuild.flds, sbuild.scan = &utq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserTextureSelect configured with the given aggregations. +func (utq *UserTextureQuery) Aggregate(fns ...AggregateFunc) *UserTextureSelect { + return utq.Select().Aggregate(fns...) +} + +func (utq *UserTextureQuery) prepareQuery(ctx context.Context) error { + for _, inter := range utq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, utq); err != nil { + return err + } + } + } + for _, f := range utq.ctx.Fields { + if !usertexture.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if utq.path != nil { + prev, err := utq.path(ctx) + if err != nil { + return err + } + utq.sql = prev + } + return nil +} + +func (utq *UserTextureQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserTexture, error) { + var ( + nodes = []*UserTexture{} + _spec = utq.querySpec() + loadedTypes = [2]bool{ + utq.withUserProfile != nil, + utq.withTexture != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserTexture).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserTexture{config: utq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(utq.modifiers) > 0 { + _spec.Modifiers = utq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, utq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := utq.withUserProfile; query != nil { + if err := utq.loadUserProfile(ctx, query, nodes, nil, + func(n *UserTexture, e *UserProfile) { n.Edges.UserProfile = e }); err != nil { + return nil, err + } + } + if query := utq.withTexture; query != nil { + if err := utq.loadTexture(ctx, query, nodes, nil, + func(n *UserTexture, e *Texture) { n.Edges.Texture = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (utq *UserTextureQuery) loadUserProfile(ctx context.Context, query *UserProfileQuery, nodes []*UserTexture, init func(*UserTexture), assign func(*UserTexture, *UserProfile)) error { + ids := make([]int, 0, len(nodes)) + nodeids := make(map[int][]*UserTexture) + for i := range nodes { + fk := nodes[i].UserProfileID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(userprofile.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_profile_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (utq *UserTextureQuery) loadTexture(ctx context.Context, query *TextureQuery, nodes []*UserTexture, init func(*UserTexture), assign func(*UserTexture, *Texture)) error { + ids := make([]int, 0, len(nodes)) + nodeids := make(map[int][]*UserTexture) + for i := range nodes { + fk := nodes[i].TextureID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(texture.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "texture_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (utq *UserTextureQuery) sqlCount(ctx context.Context) (int, error) { + _spec := utq.querySpec() + if len(utq.modifiers) > 0 { + _spec.Modifiers = utq.modifiers + } + _spec.Node.Columns = utq.ctx.Fields + if len(utq.ctx.Fields) > 0 { + _spec.Unique = utq.ctx.Unique != nil && *utq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, utq.driver, _spec) +} + +func (utq *UserTextureQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(usertexture.Table, usertexture.Columns, sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt)) + _spec.From = utq.sql + if unique := utq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if utq.path != nil { + _spec.Unique = true + } + if fields := utq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usertexture.FieldID) + for i := range fields { + if fields[i] != usertexture.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if utq.withUserProfile != nil { + _spec.Node.AddColumnOnce(usertexture.FieldUserProfileID) + } + if utq.withTexture != nil { + _spec.Node.AddColumnOnce(usertexture.FieldTextureID) + } + } + if ps := utq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := utq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := utq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := utq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (utq *UserTextureQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(utq.driver.Dialect()) + t1 := builder.Table(usertexture.Table) + columns := utq.ctx.Fields + if len(columns) == 0 { + columns = usertexture.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if utq.sql != nil { + selector = utq.sql + selector.Select(selector.Columns(columns...)...) + } + if utq.ctx.Unique != nil && *utq.ctx.Unique { + selector.Distinct() + } + for _, m := range utq.modifiers { + m(selector) + } + for _, p := range utq.predicates { + p(selector) + } + for _, p := range utq.order { + p(selector) + } + if offset := utq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := utq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (utq *UserTextureQuery) ForUpdate(opts ...sql.LockOption) *UserTextureQuery { + if utq.driver.Dialect() == dialect.Postgres { + utq.Unique(false) + } + utq.modifiers = append(utq.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return utq +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (utq *UserTextureQuery) ForShare(opts ...sql.LockOption) *UserTextureQuery { + if utq.driver.Dialect() == dialect.Postgres { + utq.Unique(false) + } + utq.modifiers = append(utq.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return utq +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (utq *UserTextureQuery) ForUpdateA(opts ...sql.LockOption) *UserTextureQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (utq *UserTextureQuery) ForShareA(opts ...sql.LockOption) *UserTextureQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForShare(opts...) +} + +// UserTextureGroupBy is the group-by builder for UserTexture entities. +type UserTextureGroupBy struct { + selector + build *UserTextureQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (utgb *UserTextureGroupBy) Aggregate(fns ...AggregateFunc) *UserTextureGroupBy { + utgb.fns = append(utgb.fns, fns...) + return utgb +} + +// Scan applies the selector query and scans the result into the given value. +func (utgb *UserTextureGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, utgb.build.ctx, "GroupBy") + if err := utgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserTextureQuery, *UserTextureGroupBy](ctx, utgb.build, utgb, utgb.build.inters, v) +} + +func (utgb *UserTextureGroupBy) sqlScan(ctx context.Context, root *UserTextureQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(utgb.fns)) + for _, fn := range utgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*utgb.flds)+len(utgb.fns)) + for _, f := range *utgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*utgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := utgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserTextureSelect is the builder for selecting fields of UserTexture entities. +type UserTextureSelect struct { + *UserTextureQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (uts *UserTextureSelect) Aggregate(fns ...AggregateFunc) *UserTextureSelect { + uts.fns = append(uts.fns, fns...) + return uts +} + +// Scan applies the selector query and scans the result into the given value. +func (uts *UserTextureSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, uts.ctx, "Select") + if err := uts.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserTextureQuery, *UserTextureSelect](ctx, uts.UserTextureQuery, uts, uts.inters, v) +} + +func (uts *UserTextureSelect) sqlScan(ctx context.Context, root *UserTextureQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(uts.fns)) + for _, fn := range uts.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*uts.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := uts.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/db/ent/usertexture_update.go b/db/ent/usertexture_update.go new file mode 100644 index 0000000..5d6c474 --- /dev/null +++ b/db/ent/usertexture_update.go @@ -0,0 +1,425 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/texture" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/db/ent/usertexture" +) + +// UserTextureUpdate is the builder for updating UserTexture entities. +type UserTextureUpdate struct { + config + hooks []Hook + mutation *UserTextureMutation +} + +// Where appends a list predicates to the UserTextureUpdate builder. +func (utu *UserTextureUpdate) Where(ps ...predicate.UserTexture) *UserTextureUpdate { + utu.mutation.Where(ps...) + return utu +} + +// SetUserProfileID sets the "user_profile_id" field. +func (utu *UserTextureUpdate) SetUserProfileID(i int) *UserTextureUpdate { + utu.mutation.SetUserProfileID(i) + return utu +} + +// SetTextureID sets the "texture_id" field. +func (utu *UserTextureUpdate) SetTextureID(i int) *UserTextureUpdate { + utu.mutation.SetTextureID(i) + return utu +} + +// SetType sets the "type" field. +func (utu *UserTextureUpdate) SetType(s string) *UserTextureUpdate { + utu.mutation.SetType(s) + return utu +} + +// SetVariant sets the "variant" field. +func (utu *UserTextureUpdate) SetVariant(s string) *UserTextureUpdate { + utu.mutation.SetVariant(s) + return utu +} + +// SetUserProfile sets the "user_profile" edge to the UserProfile entity. +func (utu *UserTextureUpdate) SetUserProfile(u *UserProfile) *UserTextureUpdate { + return utu.SetUserProfileID(u.ID) +} + +// SetTexture sets the "texture" edge to the Texture entity. +func (utu *UserTextureUpdate) SetTexture(t *Texture) *UserTextureUpdate { + return utu.SetTextureID(t.ID) +} + +// Mutation returns the UserTextureMutation object of the builder. +func (utu *UserTextureUpdate) Mutation() *UserTextureMutation { + return utu.mutation +} + +// ClearUserProfile clears the "user_profile" edge to the UserProfile entity. +func (utu *UserTextureUpdate) ClearUserProfile() *UserTextureUpdate { + utu.mutation.ClearUserProfile() + return utu +} + +// ClearTexture clears the "texture" edge to the Texture entity. +func (utu *UserTextureUpdate) ClearTexture() *UserTextureUpdate { + utu.mutation.ClearTexture() + return utu +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (utu *UserTextureUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, utu.sqlSave, utu.mutation, utu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (utu *UserTextureUpdate) SaveX(ctx context.Context) int { + affected, err := utu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (utu *UserTextureUpdate) Exec(ctx context.Context) error { + _, err := utu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utu *UserTextureUpdate) ExecX(ctx context.Context) { + if err := utu.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (utu *UserTextureUpdate) check() error { + if _, ok := utu.mutation.UserProfileID(); utu.mutation.UserProfileCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "UserTexture.user_profile"`) + } + if _, ok := utu.mutation.TextureID(); utu.mutation.TextureCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "UserTexture.texture"`) + } + return nil +} + +func (utu *UserTextureUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := utu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(usertexture.Table, usertexture.Columns, sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt)) + if ps := utu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := utu.mutation.GetType(); ok { + _spec.SetField(usertexture.FieldType, field.TypeString, value) + } + if value, ok := utu.mutation.Variant(); ok { + _spec.SetField(usertexture.FieldVariant, field.TypeString, value) + } + if utu.mutation.UserProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.UserProfileTable, + Columns: []string{usertexture.UserProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := utu.mutation.UserProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.UserProfileTable, + Columns: []string{usertexture.UserProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if utu.mutation.TextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.TextureTable, + Columns: []string{usertexture.TextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := utu.mutation.TextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.TextureTable, + Columns: []string{usertexture.TextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, utu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usertexture.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + utu.mutation.done = true + return n, nil +} + +// UserTextureUpdateOne is the builder for updating a single UserTexture entity. +type UserTextureUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserTextureMutation +} + +// SetUserProfileID sets the "user_profile_id" field. +func (utuo *UserTextureUpdateOne) SetUserProfileID(i int) *UserTextureUpdateOne { + utuo.mutation.SetUserProfileID(i) + return utuo +} + +// SetTextureID sets the "texture_id" field. +func (utuo *UserTextureUpdateOne) SetTextureID(i int) *UserTextureUpdateOne { + utuo.mutation.SetTextureID(i) + return utuo +} + +// SetType sets the "type" field. +func (utuo *UserTextureUpdateOne) SetType(s string) *UserTextureUpdateOne { + utuo.mutation.SetType(s) + return utuo +} + +// SetVariant sets the "variant" field. +func (utuo *UserTextureUpdateOne) SetVariant(s string) *UserTextureUpdateOne { + utuo.mutation.SetVariant(s) + return utuo +} + +// SetUserProfile sets the "user_profile" edge to the UserProfile entity. +func (utuo *UserTextureUpdateOne) SetUserProfile(u *UserProfile) *UserTextureUpdateOne { + return utuo.SetUserProfileID(u.ID) +} + +// SetTexture sets the "texture" edge to the Texture entity. +func (utuo *UserTextureUpdateOne) SetTexture(t *Texture) *UserTextureUpdateOne { + return utuo.SetTextureID(t.ID) +} + +// Mutation returns the UserTextureMutation object of the builder. +func (utuo *UserTextureUpdateOne) Mutation() *UserTextureMutation { + return utuo.mutation +} + +// ClearUserProfile clears the "user_profile" edge to the UserProfile entity. +func (utuo *UserTextureUpdateOne) ClearUserProfile() *UserTextureUpdateOne { + utuo.mutation.ClearUserProfile() + return utuo +} + +// ClearTexture clears the "texture" edge to the Texture entity. +func (utuo *UserTextureUpdateOne) ClearTexture() *UserTextureUpdateOne { + utuo.mutation.ClearTexture() + return utuo +} + +// Where appends a list predicates to the UserTextureUpdate builder. +func (utuo *UserTextureUpdateOne) Where(ps ...predicate.UserTexture) *UserTextureUpdateOne { + utuo.mutation.Where(ps...) + return utuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (utuo *UserTextureUpdateOne) Select(field string, fields ...string) *UserTextureUpdateOne { + utuo.fields = append([]string{field}, fields...) + return utuo +} + +// Save executes the query and returns the updated UserTexture entity. +func (utuo *UserTextureUpdateOne) Save(ctx context.Context) (*UserTexture, error) { + return withHooks(ctx, utuo.sqlSave, utuo.mutation, utuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (utuo *UserTextureUpdateOne) SaveX(ctx context.Context) *UserTexture { + node, err := utuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (utuo *UserTextureUpdateOne) Exec(ctx context.Context) error { + _, err := utuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utuo *UserTextureUpdateOne) ExecX(ctx context.Context) { + if err := utuo.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (utuo *UserTextureUpdateOne) check() error { + if _, ok := utuo.mutation.UserProfileID(); utuo.mutation.UserProfileCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "UserTexture.user_profile"`) + } + if _, ok := utuo.mutation.TextureID(); utuo.mutation.TextureCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "UserTexture.texture"`) + } + return nil +} + +func (utuo *UserTextureUpdateOne) sqlSave(ctx context.Context) (_node *UserTexture, err error) { + if err := utuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(usertexture.Table, usertexture.Columns, sqlgraph.NewFieldSpec(usertexture.FieldID, field.TypeInt)) + id, ok := utuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "UserTexture.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := utuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usertexture.FieldID) + for _, f := range fields { + if !usertexture.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != usertexture.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := utuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := utuo.mutation.GetType(); ok { + _spec.SetField(usertexture.FieldType, field.TypeString, value) + } + if value, ok := utuo.mutation.Variant(); ok { + _spec.SetField(usertexture.FieldVariant, field.TypeString, value) + } + if utuo.mutation.UserProfileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.UserProfileTable, + Columns: []string{usertexture.UserProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := utuo.mutation.UserProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.UserProfileTable, + Columns: []string{usertexture.UserProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(userprofile.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if utuo.mutation.TextureCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.TextureTable, + Columns: []string{usertexture.TextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := utuo.mutation.TextureIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: usertexture.TextureTable, + Columns: []string{usertexture.TextureColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(texture.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &UserTexture{config: utuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, utuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usertexture.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + utuo.mutation.done = true + return _node, nil +} diff --git a/db/ent/usertoken.go b/db/ent/usertoken.go new file mode 100644 index 0000000..c944538 --- /dev/null +++ b/db/ent/usertoken.go @@ -0,0 +1,142 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// UserToken is the model entity for the UserToken schema. +type UserToken struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // TokenID holds the value of the "token_id" field. + TokenID uint64 `json:"token_id,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserTokenQuery when eager-loading is set. + Edges UserTokenEdges `json:"edges"` + user_token *int + selectValues sql.SelectValues +} + +// UserTokenEdges holds the relations/edges for other nodes in the graph. +type UserTokenEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserTokenEdges) UserOrErr() (*User, error) { + if e.loadedTypes[0] { + if e.User == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: user.Label} + } + return e.User, nil + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserToken) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case usertoken.FieldID, usertoken.FieldTokenID: + values[i] = new(sql.NullInt64) + case usertoken.ForeignKeys[0]: // user_token + values[i] = new(sql.NullInt64) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserToken fields. +func (ut *UserToken) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case usertoken.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + ut.ID = int(value.Int64) + case usertoken.FieldTokenID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field token_id", values[i]) + } else if value.Valid { + ut.TokenID = uint64(value.Int64) + } + case usertoken.ForeignKeys[0]: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for edge-field user_token", value) + } else if value.Valid { + ut.user_token = new(int) + *ut.user_token = int(value.Int64) + } + default: + ut.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserToken. +// This includes values selected through modifiers, order, etc. +func (ut *UserToken) Value(name string) (ent.Value, error) { + return ut.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the UserToken entity. +func (ut *UserToken) QueryUser() *UserQuery { + return NewUserTokenClient(ut.config).QueryUser(ut) +} + +// Update returns a builder for updating this UserToken. +// Note that you need to call UserToken.Unwrap() before calling this method if this UserToken +// was returned from a transaction, and the transaction was committed or rolled back. +func (ut *UserToken) Update() *UserTokenUpdateOne { + return NewUserTokenClient(ut.config).UpdateOne(ut) +} + +// Unwrap unwraps the UserToken entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (ut *UserToken) Unwrap() *UserToken { + _tx, ok := ut.config.driver.(*txDriver) + if !ok { + panic("ent: UserToken is not a transactional entity") + } + ut.config.driver = _tx.drv + return ut +} + +// String implements the fmt.Stringer. +func (ut *UserToken) String() string { + var builder strings.Builder + builder.WriteString("UserToken(") + builder.WriteString(fmt.Sprintf("id=%v, ", ut.ID)) + builder.WriteString("token_id=") + builder.WriteString(fmt.Sprintf("%v", ut.TokenID)) + builder.WriteByte(')') + return builder.String() +} + +// UserTokens is a parsable slice of UserToken. +type UserTokens []*UserToken diff --git a/db/ent/usertoken/usertoken.go b/db/ent/usertoken/usertoken.go new file mode 100644 index 0000000..a9d8958 --- /dev/null +++ b/db/ent/usertoken/usertoken.go @@ -0,0 +1,82 @@ +// Code generated by ent, DO NOT EDIT. + +package usertoken + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the usertoken type in the database. + Label = "user_token" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldTokenID holds the string denoting the token_id field in the database. + FieldTokenID = "token_id" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the usertoken in the database. + Table = "user_tokens" + // UserTable is the table that holds the user relation/edge. + UserTable = "user_tokens" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_token" +) + +// Columns holds all SQL columns for usertoken fields. +var Columns = []string{ + FieldID, + FieldTokenID, +} + +// ForeignKeys holds the SQL foreign-keys that are owned by the "user_tokens" +// table and are not defined as standalone fields in the schema. +var ForeignKeys = []string{ + "user_token", +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + for i := range ForeignKeys { + if column == ForeignKeys[i] { + return true + } + } + return false +} + +// OrderOption defines the ordering options for the UserToken queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByTokenID orders the results by the token_id field. +func ByTokenID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTokenID, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, UserTable, UserColumn), + ) +} diff --git a/db/ent/usertoken/where.go b/db/ent/usertoken/where.go new file mode 100644 index 0000000..fb39a0f --- /dev/null +++ b/db/ent/usertoken/where.go @@ -0,0 +1,154 @@ +// Code generated by ent, DO NOT EDIT. + +package usertoken + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/xmdhs/authlib-skin/db/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.UserToken { + return predicate.UserToken(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.UserToken { + return predicate.UserToken(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.UserToken { + return predicate.UserToken(sql.FieldLTE(FieldID, id)) +} + +// TokenID applies equality check predicate on the "token_id" field. It's identical to TokenIDEQ. +func TokenID(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldEQ(FieldTokenID, v)) +} + +// TokenIDEQ applies the EQ predicate on the "token_id" field. +func TokenIDEQ(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldEQ(FieldTokenID, v)) +} + +// TokenIDNEQ applies the NEQ predicate on the "token_id" field. +func TokenIDNEQ(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldNEQ(FieldTokenID, v)) +} + +// TokenIDIn applies the In predicate on the "token_id" field. +func TokenIDIn(vs ...uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldIn(FieldTokenID, vs...)) +} + +// TokenIDNotIn applies the NotIn predicate on the "token_id" field. +func TokenIDNotIn(vs ...uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldNotIn(FieldTokenID, vs...)) +} + +// TokenIDGT applies the GT predicate on the "token_id" field. +func TokenIDGT(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldGT(FieldTokenID, v)) +} + +// TokenIDGTE applies the GTE predicate on the "token_id" field. +func TokenIDGTE(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldGTE(FieldTokenID, v)) +} + +// TokenIDLT applies the LT predicate on the "token_id" field. +func TokenIDLT(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldLT(FieldTokenID, v)) +} + +// TokenIDLTE applies the LTE predicate on the "token_id" field. +func TokenIDLTE(v uint64) predicate.UserToken { + return predicate.UserToken(sql.FieldLTE(FieldTokenID, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.UserToken { + return predicate.UserToken(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.UserToken { + return predicate.UserToken(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserToken) predicate.UserToken { + return predicate.UserToken(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserToken) predicate.UserToken { + return predicate.UserToken(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserToken) predicate.UserToken { + return predicate.UserToken(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/db/ent/usertoken_create.go b/db/ent/usertoken_create.go new file mode 100644 index 0000000..3bf2219 --- /dev/null +++ b/db/ent/usertoken_create.go @@ -0,0 +1,216 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// UserTokenCreate is the builder for creating a UserToken entity. +type UserTokenCreate struct { + config + mutation *UserTokenMutation + hooks []Hook +} + +// SetTokenID sets the "token_id" field. +func (utc *UserTokenCreate) SetTokenID(u uint64) *UserTokenCreate { + utc.mutation.SetTokenID(u) + return utc +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (utc *UserTokenCreate) SetUserID(id int) *UserTokenCreate { + utc.mutation.SetUserID(id) + return utc +} + +// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. +func (utc *UserTokenCreate) SetNillableUserID(id *int) *UserTokenCreate { + if id != nil { + utc = utc.SetUserID(*id) + } + return utc +} + +// SetUser sets the "user" edge to the User entity. +func (utc *UserTokenCreate) SetUser(u *User) *UserTokenCreate { + return utc.SetUserID(u.ID) +} + +// Mutation returns the UserTokenMutation object of the builder. +func (utc *UserTokenCreate) Mutation() *UserTokenMutation { + return utc.mutation +} + +// Save creates the UserToken in the database. +func (utc *UserTokenCreate) Save(ctx context.Context) (*UserToken, error) { + return withHooks(ctx, utc.sqlSave, utc.mutation, utc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (utc *UserTokenCreate) SaveX(ctx context.Context) *UserToken { + v, err := utc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (utc *UserTokenCreate) Exec(ctx context.Context) error { + _, err := utc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utc *UserTokenCreate) ExecX(ctx context.Context) { + if err := utc.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (utc *UserTokenCreate) check() error { + if _, ok := utc.mutation.TokenID(); !ok { + return &ValidationError{Name: "token_id", err: errors.New(`ent: missing required field "UserToken.token_id"`)} + } + return nil +} + +func (utc *UserTokenCreate) sqlSave(ctx context.Context) (*UserToken, error) { + if err := utc.check(); err != nil { + return nil, err + } + _node, _spec := utc.createSpec() + if err := sqlgraph.CreateNode(ctx, utc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + utc.mutation.id = &_node.ID + utc.mutation.done = true + return _node, nil +} + +func (utc *UserTokenCreate) createSpec() (*UserToken, *sqlgraph.CreateSpec) { + var ( + _node = &UserToken{config: utc.config} + _spec = sqlgraph.NewCreateSpec(usertoken.Table, sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt)) + ) + if value, ok := utc.mutation.TokenID(); ok { + _spec.SetField(usertoken.FieldTokenID, field.TypeUint64, value) + _node.TokenID = value + } + if nodes := utc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: usertoken.UserTable, + Columns: []string{usertoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.user_token = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// UserTokenCreateBulk is the builder for creating many UserToken entities in bulk. +type UserTokenCreateBulk struct { + config + builders []*UserTokenCreate +} + +// Save creates the UserToken entities in the database. +func (utcb *UserTokenCreateBulk) Save(ctx context.Context) ([]*UserToken, error) { + specs := make([]*sqlgraph.CreateSpec, len(utcb.builders)) + nodes := make([]*UserToken, len(utcb.builders)) + mutators := make([]Mutator, len(utcb.builders)) + for i := range utcb.builders { + func(i int, root context.Context) { + builder := utcb.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserTokenMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, utcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, utcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, utcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (utcb *UserTokenCreateBulk) SaveX(ctx context.Context) []*UserToken { + v, err := utcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (utcb *UserTokenCreateBulk) Exec(ctx context.Context) error { + _, err := utcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utcb *UserTokenCreateBulk) ExecX(ctx context.Context) { + if err := utcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/usertoken_delete.go b/db/ent/usertoken_delete.go new file mode 100644 index 0000000..1f6a2ba --- /dev/null +++ b/db/ent/usertoken_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// UserTokenDelete is the builder for deleting a UserToken entity. +type UserTokenDelete struct { + config + hooks []Hook + mutation *UserTokenMutation +} + +// Where appends a list predicates to the UserTokenDelete builder. +func (utd *UserTokenDelete) Where(ps ...predicate.UserToken) *UserTokenDelete { + utd.mutation.Where(ps...) + return utd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (utd *UserTokenDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, utd.sqlExec, utd.mutation, utd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (utd *UserTokenDelete) ExecX(ctx context.Context) int { + n, err := utd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (utd *UserTokenDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(usertoken.Table, sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt)) + if ps := utd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, utd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + utd.mutation.done = true + return affected, err +} + +// UserTokenDeleteOne is the builder for deleting a single UserToken entity. +type UserTokenDeleteOne struct { + utd *UserTokenDelete +} + +// Where appends a list predicates to the UserTokenDelete builder. +func (utdo *UserTokenDeleteOne) Where(ps ...predicate.UserToken) *UserTokenDeleteOne { + utdo.utd.mutation.Where(ps...) + return utdo +} + +// Exec executes the deletion query. +func (utdo *UserTokenDeleteOne) Exec(ctx context.Context) error { + n, err := utdo.utd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{usertoken.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (utdo *UserTokenDeleteOne) ExecX(ctx context.Context) { + if err := utdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/db/ent/usertoken_query.go b/db/ent/usertoken_query.go new file mode 100644 index 0000000..6a6bc9d --- /dev/null +++ b/db/ent/usertoken_query.go @@ -0,0 +1,670 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// UserTokenQuery is the builder for querying UserToken entities. +type UserTokenQuery struct { + config + ctx *QueryContext + order []usertoken.OrderOption + inters []Interceptor + predicates []predicate.UserToken + withUser *UserQuery + withFKs bool + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserTokenQuery builder. +func (utq *UserTokenQuery) Where(ps ...predicate.UserToken) *UserTokenQuery { + utq.predicates = append(utq.predicates, ps...) + return utq +} + +// Limit the number of records to be returned by this query. +func (utq *UserTokenQuery) Limit(limit int) *UserTokenQuery { + utq.ctx.Limit = &limit + return utq +} + +// Offset to start from. +func (utq *UserTokenQuery) Offset(offset int) *UserTokenQuery { + utq.ctx.Offset = &offset + return utq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (utq *UserTokenQuery) Unique(unique bool) *UserTokenQuery { + utq.ctx.Unique = &unique + return utq +} + +// Order specifies how the records should be ordered. +func (utq *UserTokenQuery) Order(o ...usertoken.OrderOption) *UserTokenQuery { + utq.order = append(utq.order, o...) + return utq +} + +// QueryUser chains the current query on the "user" edge. +func (utq *UserTokenQuery) QueryUser() *UserQuery { + query := (&UserClient{config: utq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := utq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := utq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usertoken.Table, usertoken.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, usertoken.UserTable, usertoken.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(utq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserToken entity from the query. +// Returns a *NotFoundError when no UserToken was found. +func (utq *UserTokenQuery) First(ctx context.Context) (*UserToken, error) { + nodes, err := utq.Limit(1).All(setContextOp(ctx, utq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{usertoken.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (utq *UserTokenQuery) FirstX(ctx context.Context) *UserToken { + node, err := utq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserToken ID from the query. +// Returns a *NotFoundError when no UserToken ID was found. +func (utq *UserTokenQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = utq.Limit(1).IDs(setContextOp(ctx, utq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{usertoken.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (utq *UserTokenQuery) FirstIDX(ctx context.Context) int { + id, err := utq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserToken entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserToken entity is found. +// Returns a *NotFoundError when no UserToken entities are found. +func (utq *UserTokenQuery) Only(ctx context.Context) (*UserToken, error) { + nodes, err := utq.Limit(2).All(setContextOp(ctx, utq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{usertoken.Label} + default: + return nil, &NotSingularError{usertoken.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (utq *UserTokenQuery) OnlyX(ctx context.Context) *UserToken { + node, err := utq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserToken ID in the query. +// Returns a *NotSingularError when more than one UserToken ID is found. +// Returns a *NotFoundError when no entities are found. +func (utq *UserTokenQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = utq.Limit(2).IDs(setContextOp(ctx, utq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{usertoken.Label} + default: + err = &NotSingularError{usertoken.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (utq *UserTokenQuery) OnlyIDX(ctx context.Context) int { + id, err := utq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserTokens. +func (utq *UserTokenQuery) All(ctx context.Context) ([]*UserToken, error) { + ctx = setContextOp(ctx, utq.ctx, "All") + if err := utq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserToken, *UserTokenQuery]() + return withInterceptors[[]*UserToken](ctx, utq, qr, utq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (utq *UserTokenQuery) AllX(ctx context.Context) []*UserToken { + nodes, err := utq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserToken IDs. +func (utq *UserTokenQuery) IDs(ctx context.Context) (ids []int, err error) { + if utq.ctx.Unique == nil && utq.path != nil { + utq.Unique(true) + } + ctx = setContextOp(ctx, utq.ctx, "IDs") + if err = utq.Select(usertoken.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (utq *UserTokenQuery) IDsX(ctx context.Context) []int { + ids, err := utq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (utq *UserTokenQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, utq.ctx, "Count") + if err := utq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, utq, querierCount[*UserTokenQuery](), utq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (utq *UserTokenQuery) CountX(ctx context.Context) int { + count, err := utq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (utq *UserTokenQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, utq.ctx, "Exist") + switch _, err := utq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (utq *UserTokenQuery) ExistX(ctx context.Context) bool { + exist, err := utq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserTokenQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (utq *UserTokenQuery) Clone() *UserTokenQuery { + if utq == nil { + return nil + } + return &UserTokenQuery{ + config: utq.config, + ctx: utq.ctx.Clone(), + order: append([]usertoken.OrderOption{}, utq.order...), + inters: append([]Interceptor{}, utq.inters...), + predicates: append([]predicate.UserToken{}, utq.predicates...), + withUser: utq.withUser.Clone(), + // clone intermediate query. + sql: utq.sql.Clone(), + path: utq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (utq *UserTokenQuery) WithUser(opts ...func(*UserQuery)) *UserTokenQuery { + query := (&UserClient{config: utq.config}).Query() + for _, opt := range opts { + opt(query) + } + utq.withUser = query + return utq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// TokenID uint64 `json:"token_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserToken.Query(). +// GroupBy(usertoken.FieldTokenID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (utq *UserTokenQuery) GroupBy(field string, fields ...string) *UserTokenGroupBy { + utq.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserTokenGroupBy{build: utq} + grbuild.flds = &utq.ctx.Fields + grbuild.label = usertoken.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// TokenID uint64 `json:"token_id,omitempty"` +// } +// +// client.UserToken.Query(). +// Select(usertoken.FieldTokenID). +// Scan(ctx, &v) +func (utq *UserTokenQuery) Select(fields ...string) *UserTokenSelect { + utq.ctx.Fields = append(utq.ctx.Fields, fields...) + sbuild := &UserTokenSelect{UserTokenQuery: utq} + sbuild.label = usertoken.Label + sbuild.flds, sbuild.scan = &utq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserTokenSelect configured with the given aggregations. +func (utq *UserTokenQuery) Aggregate(fns ...AggregateFunc) *UserTokenSelect { + return utq.Select().Aggregate(fns...) +} + +func (utq *UserTokenQuery) prepareQuery(ctx context.Context) error { + for _, inter := range utq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, utq); err != nil { + return err + } + } + } + for _, f := range utq.ctx.Fields { + if !usertoken.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if utq.path != nil { + prev, err := utq.path(ctx) + if err != nil { + return err + } + utq.sql = prev + } + return nil +} + +func (utq *UserTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserToken, error) { + var ( + nodes = []*UserToken{} + withFKs = utq.withFKs + _spec = utq.querySpec() + loadedTypes = [1]bool{ + utq.withUser != nil, + } + ) + if utq.withUser != nil { + withFKs = true + } + if withFKs { + _spec.Node.Columns = append(_spec.Node.Columns, usertoken.ForeignKeys...) + } + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserToken).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserToken{config: utq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(utq.modifiers) > 0 { + _spec.Modifiers = utq.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, utq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := utq.withUser; query != nil { + if err := utq.loadUser(ctx, query, nodes, nil, + func(n *UserToken, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (utq *UserTokenQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*UserToken, init func(*UserToken), assign func(*UserToken, *User)) error { + ids := make([]int, 0, len(nodes)) + nodeids := make(map[int][]*UserToken) + for i := range nodes { + if nodes[i].user_token == nil { + continue + } + fk := *nodes[i].user_token + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_token" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (utq *UserTokenQuery) sqlCount(ctx context.Context) (int, error) { + _spec := utq.querySpec() + if len(utq.modifiers) > 0 { + _spec.Modifiers = utq.modifiers + } + _spec.Node.Columns = utq.ctx.Fields + if len(utq.ctx.Fields) > 0 { + _spec.Unique = utq.ctx.Unique != nil && *utq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, utq.driver, _spec) +} + +func (utq *UserTokenQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(usertoken.Table, usertoken.Columns, sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt)) + _spec.From = utq.sql + if unique := utq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if utq.path != nil { + _spec.Unique = true + } + if fields := utq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usertoken.FieldID) + for i := range fields { + if fields[i] != usertoken.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := utq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := utq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := utq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := utq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (utq *UserTokenQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(utq.driver.Dialect()) + t1 := builder.Table(usertoken.Table) + columns := utq.ctx.Fields + if len(columns) == 0 { + columns = usertoken.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if utq.sql != nil { + selector = utq.sql + selector.Select(selector.Columns(columns...)...) + } + if utq.ctx.Unique != nil && *utq.ctx.Unique { + selector.Distinct() + } + for _, m := range utq.modifiers { + m(selector) + } + for _, p := range utq.predicates { + p(selector) + } + for _, p := range utq.order { + p(selector) + } + if offset := utq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := utq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (utq *UserTokenQuery) ForUpdate(opts ...sql.LockOption) *UserTokenQuery { + if utq.driver.Dialect() == dialect.Postgres { + utq.Unique(false) + } + utq.modifiers = append(utq.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return utq +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (utq *UserTokenQuery) ForShare(opts ...sql.LockOption) *UserTokenQuery { + if utq.driver.Dialect() == dialect.Postgres { + utq.Unique(false) + } + utq.modifiers = append(utq.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return utq +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (utq *UserTokenQuery) ForUpdateA(opts ...sql.LockOption) *UserTokenQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (utq *UserTokenQuery) ForShareA(opts ...sql.LockOption) *UserTokenQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForShare(opts...) +} + +// UserTokenGroupBy is the group-by builder for UserToken entities. +type UserTokenGroupBy struct { + selector + build *UserTokenQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (utgb *UserTokenGroupBy) Aggregate(fns ...AggregateFunc) *UserTokenGroupBy { + utgb.fns = append(utgb.fns, fns...) + return utgb +} + +// Scan applies the selector query and scans the result into the given value. +func (utgb *UserTokenGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, utgb.build.ctx, "GroupBy") + if err := utgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserTokenQuery, *UserTokenGroupBy](ctx, utgb.build, utgb, utgb.build.inters, v) +} + +func (utgb *UserTokenGroupBy) sqlScan(ctx context.Context, root *UserTokenQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(utgb.fns)) + for _, fn := range utgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*utgb.flds)+len(utgb.fns)) + for _, f := range *utgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*utgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := utgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserTokenSelect is the builder for selecting fields of UserToken entities. +type UserTokenSelect struct { + *UserTokenQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (uts *UserTokenSelect) Aggregate(fns ...AggregateFunc) *UserTokenSelect { + uts.fns = append(uts.fns, fns...) + return uts +} + +// Scan applies the selector query and scans the result into the given value. +func (uts *UserTokenSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, uts.ctx, "Select") + if err := uts.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserTokenQuery, *UserTokenSelect](ctx, uts.UserTokenQuery, uts, uts.inters, v) +} + +func (uts *UserTokenSelect) sqlScan(ctx context.Context, root *UserTokenQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(uts.fns)) + for _, fn := range uts.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*uts.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := uts.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/db/ent/usertoken_update.go b/db/ent/usertoken_update.go new file mode 100644 index 0000000..457eeeb --- /dev/null +++ b/db/ent/usertoken_update.go @@ -0,0 +1,322 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" +) + +// UserTokenUpdate is the builder for updating UserToken entities. +type UserTokenUpdate struct { + config + hooks []Hook + mutation *UserTokenMutation +} + +// Where appends a list predicates to the UserTokenUpdate builder. +func (utu *UserTokenUpdate) Where(ps ...predicate.UserToken) *UserTokenUpdate { + utu.mutation.Where(ps...) + return utu +} + +// SetTokenID sets the "token_id" field. +func (utu *UserTokenUpdate) SetTokenID(u uint64) *UserTokenUpdate { + utu.mutation.ResetTokenID() + utu.mutation.SetTokenID(u) + return utu +} + +// AddTokenID adds u to the "token_id" field. +func (utu *UserTokenUpdate) AddTokenID(u int64) *UserTokenUpdate { + utu.mutation.AddTokenID(u) + return utu +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (utu *UserTokenUpdate) SetUserID(id int) *UserTokenUpdate { + utu.mutation.SetUserID(id) + return utu +} + +// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. +func (utu *UserTokenUpdate) SetNillableUserID(id *int) *UserTokenUpdate { + if id != nil { + utu = utu.SetUserID(*id) + } + return utu +} + +// SetUser sets the "user" edge to the User entity. +func (utu *UserTokenUpdate) SetUser(u *User) *UserTokenUpdate { + return utu.SetUserID(u.ID) +} + +// Mutation returns the UserTokenMutation object of the builder. +func (utu *UserTokenUpdate) Mutation() *UserTokenMutation { + return utu.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (utu *UserTokenUpdate) ClearUser() *UserTokenUpdate { + utu.mutation.ClearUser() + return utu +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (utu *UserTokenUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, utu.sqlSave, utu.mutation, utu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (utu *UserTokenUpdate) SaveX(ctx context.Context) int { + affected, err := utu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (utu *UserTokenUpdate) Exec(ctx context.Context) error { + _, err := utu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utu *UserTokenUpdate) ExecX(ctx context.Context) { + if err := utu.Exec(ctx); err != nil { + panic(err) + } +} + +func (utu *UserTokenUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := sqlgraph.NewUpdateSpec(usertoken.Table, usertoken.Columns, sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt)) + if ps := utu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := utu.mutation.TokenID(); ok { + _spec.SetField(usertoken.FieldTokenID, field.TypeUint64, value) + } + if value, ok := utu.mutation.AddedTokenID(); ok { + _spec.AddField(usertoken.FieldTokenID, field.TypeUint64, value) + } + if utu.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: usertoken.UserTable, + Columns: []string{usertoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := utu.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: usertoken.UserTable, + Columns: []string{usertoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, utu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usertoken.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + utu.mutation.done = true + return n, nil +} + +// UserTokenUpdateOne is the builder for updating a single UserToken entity. +type UserTokenUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserTokenMutation +} + +// SetTokenID sets the "token_id" field. +func (utuo *UserTokenUpdateOne) SetTokenID(u uint64) *UserTokenUpdateOne { + utuo.mutation.ResetTokenID() + utuo.mutation.SetTokenID(u) + return utuo +} + +// AddTokenID adds u to the "token_id" field. +func (utuo *UserTokenUpdateOne) AddTokenID(u int64) *UserTokenUpdateOne { + utuo.mutation.AddTokenID(u) + return utuo +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (utuo *UserTokenUpdateOne) SetUserID(id int) *UserTokenUpdateOne { + utuo.mutation.SetUserID(id) + return utuo +} + +// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. +func (utuo *UserTokenUpdateOne) SetNillableUserID(id *int) *UserTokenUpdateOne { + if id != nil { + utuo = utuo.SetUserID(*id) + } + return utuo +} + +// SetUser sets the "user" edge to the User entity. +func (utuo *UserTokenUpdateOne) SetUser(u *User) *UserTokenUpdateOne { + return utuo.SetUserID(u.ID) +} + +// Mutation returns the UserTokenMutation object of the builder. +func (utuo *UserTokenUpdateOne) Mutation() *UserTokenMutation { + return utuo.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (utuo *UserTokenUpdateOne) ClearUser() *UserTokenUpdateOne { + utuo.mutation.ClearUser() + return utuo +} + +// Where appends a list predicates to the UserTokenUpdate builder. +func (utuo *UserTokenUpdateOne) Where(ps ...predicate.UserToken) *UserTokenUpdateOne { + utuo.mutation.Where(ps...) + return utuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (utuo *UserTokenUpdateOne) Select(field string, fields ...string) *UserTokenUpdateOne { + utuo.fields = append([]string{field}, fields...) + return utuo +} + +// Save executes the query and returns the updated UserToken entity. +func (utuo *UserTokenUpdateOne) Save(ctx context.Context) (*UserToken, error) { + return withHooks(ctx, utuo.sqlSave, utuo.mutation, utuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (utuo *UserTokenUpdateOne) SaveX(ctx context.Context) *UserToken { + node, err := utuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (utuo *UserTokenUpdateOne) Exec(ctx context.Context) error { + _, err := utuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (utuo *UserTokenUpdateOne) ExecX(ctx context.Context) { + if err := utuo.Exec(ctx); err != nil { + panic(err) + } +} + +func (utuo *UserTokenUpdateOne) sqlSave(ctx context.Context) (_node *UserToken, err error) { + _spec := sqlgraph.NewUpdateSpec(usertoken.Table, usertoken.Columns, sqlgraph.NewFieldSpec(usertoken.FieldID, field.TypeInt)) + id, ok := utuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "UserToken.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := utuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usertoken.FieldID) + for _, f := range fields { + if !usertoken.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != usertoken.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := utuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := utuo.mutation.TokenID(); ok { + _spec.SetField(usertoken.FieldTokenID, field.TypeUint64, value) + } + if value, ok := utuo.mutation.AddedTokenID(); ok { + _spec.AddField(usertoken.FieldTokenID, field.TypeUint64, value) + } + if utuo.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: usertoken.UserTable, + Columns: []string{usertoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := utuo.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: usertoken.UserTable, + Columns: []string{usertoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &UserToken{config: utuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, utuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usertoken.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + utuo.mutation.done = true + return _node, nil +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e28dc2b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" + +networks: + traefik: + name: traefik + external: true + + +services: + authlib-skin: + build: . + container_name: authlib-skin + volumes: + - /srv/server/authlib-skin:/app/config + restart: always + labels: + - traefik.enable=true + - traefik.http.routers.authlib-skin.rule=Host(`auth.hfbz.net`) + - traefik.http.routers.authlib-skin.entrypoints=web,websecure + - traefik.http.routers.authlib-skin.middlewares=CORS@file + - traefik.http.services.authlib-skin.loadbalancer.server.port=8080 + networks: + - traefik diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..44c88d6 --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +VITE_APIADDR = '' \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..120f8ea --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1 @@ +VITE_APIADDR = 'http://127.0.0.1:8080' \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..1ebe379 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,27 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..d3c14a6 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 皮肤站 + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..32a53fb --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,39 @@ +{ + "name": "@xmdhs/authlib-skin", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@marsidev/react-turnstile": "^1.0.2", + "@mui/icons-material": "^6.1.7", + "@mui/material": "^6.1.7", + "ahooks": "^3.8.1", + "immer": "^10.1.1", + "jotai": "^2.10.2", + "mui-file-input": "^6.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "skinview3d": "^3.1.0", + "tilg": "^0.1.1" + }, + "devDependencies": { + "@types/node": "^20.17.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-legacy": "^5.4.3", + "@vitejs/plugin-react": "^4.3.3", + "@vitejs/plugin-react-swc": "^3.7.1", + "terser": "^5.36.0", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } +} \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..ff61268 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,3195 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@emotion/react': + specifier: ^11.13.3 + version: 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/styled': + specifier: ^11.13.0 + version: 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@marsidev/react-turnstile': + specifier: ^1.0.2 + version: 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/icons-material': + specifier: ^6.1.7 + version: 6.1.7(@mui/material@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@mui/material': + specifier: ^6.1.7 + version: 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ahooks: + specifier: ^3.8.1 + version: 3.8.1(react@18.3.1) + immer: + specifier: ^10.1.1 + version: 10.1.1 + jotai: + specifier: ^2.10.2 + version: 2.10.2(@types/react@18.3.12)(react@18.3.1) + mui-file-input: + specifier: ^6.0.0 + version: 6.0.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/material@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.28.0 + version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + skinview3d: + specifier: ^3.1.0 + version: 3.1.0 + tilg: + specifier: ^0.1.1 + version: 0.1.1(react@18.3.1) + devDependencies: + '@types/node': + specifier: ^20.17.6 + version: 20.17.6 + '@types/react': + specifier: ^18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + '@vitejs/plugin-legacy': + specifier: ^5.4.3 + version: 5.4.3(terser@5.36.0)(vite@5.4.11(@types/node@20.17.6)(terser@5.36.0)) + '@vitejs/plugin-react': + specifier: ^4.3.3 + version: 4.3.3(vite@5.4.11(@types/node@20.17.6)(terser@5.36.0)) + '@vitejs/plugin-react-swc': + specifier: ^3.7.1 + version: 3.7.1(vite@5.4.11(@types/node@20.17.6)(terser@5.36.0)) + terser: + specifier: ^5.36.0 + version: 5.36.0 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vite: + specifier: ^5.4.11 + version: 5.4.11(@types/node@20.17.6)(terser@5.36.0) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': + resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.25.9': + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.25.9': + resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.3': + resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.25.9': + resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.25.9': + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-simple-access@7.25.9': + resolution: {integrity: sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.25.9': + resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.25.9': + resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.25.9': + resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.25.9': + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.25.9': + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.25.9': + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.25.9': + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.25.9': + resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.25.9': + resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.25.9': + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.25.9': + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.25.9': + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.25.9': + resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.25.9': + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9': + resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.25.9': + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.25.9': + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.9': + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.25.9': + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.25.9': + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.9': + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.25.9': + resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.25.9': + resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.0': + resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + + '@emotion/babel-plugin@11.12.0': + resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} + + '@emotion/cache@11.13.1': + resolution: {integrity: sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.3.1': + resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.13.3': + resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.2': + resolution: {integrity: sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.13.0': + resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.1.0': + resolution: {integrity: sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.1': + resolution: {integrity: sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@marsidev/react-turnstile@1.0.2': + resolution: {integrity: sha512-YkCtJVaCzZ1kcmhPsiFTmTXVughoNzEMRsNHcmTG0K5OdbCQfAG67Q6d5Ze+A72vrHbvVZkvcLgUbldeGcbRjQ==} + peerDependencies: + react: ^17.0.2 || ^18.0.0 + react-dom: ^17.0.2 || ^18.0.0 + + '@mui/core-downloads-tracker@6.1.7': + resolution: {integrity: sha512-POuIBi80BZBogQkG4PQKIGwy4QFwB+kOr+OI4k7Znh7LqMAIhwB9OC00l6M+w1GrZJYj3T8R5WX8G6QAIvoVEw==} + + '@mui/icons-material@6.1.7': + resolution: {integrity: sha512-RGzkeHNArIVy5ZQ12bq/8VYNeICEyngngsFskTJ/2hYKhIeIII3iRGtaZaSvLpXh7h3Fg3VKTulT+QU0w5K4XQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^6.1.7 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material@6.1.7': + resolution: {integrity: sha512-KsjujQL/A2hLd1PV3QboF+W6SSL5QqH6ZlSuQoeYz9r69+TnyBFIevbYLxdjJcJmGBjigL5pfpn7hTGop+vhSg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^6.1.7 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@6.1.7': + resolution: {integrity: sha512-uLbfUSsug5K0LVkv0PI6Flste3le8+6WSL2omdTiYde93P89Qr7pKr8TA6d2yXfr+Bm+SvD8/fGnkaRwFkryuQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@6.1.7': + resolution: {integrity: sha512-Ou4CxN7MQmwrfG1Pu6EYjPgPChQXxPDJrwgizLXlRPOad5qAq4gYXRuzrGQ2DfGjjwmJhjI8T6A0SeapAZPGig==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@6.1.7': + resolution: {integrity: sha512-qbMGgcC/FodpuRSfjXlEDdbNQaW++eATh0vNBcPUv2/YXSpReoOpoT9FhogxEBNks+aQViDXBRZKh6HX2fVmwg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.2.19': + resolution: {integrity: sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@6.1.7': + resolution: {integrity: sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@remix-run/router@1.21.0': + resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} + engines: {node: '>=14.0.0'} + + '@rollup/rollup-android-arm-eabi@4.27.2': + resolution: {integrity: sha512-Tj+j7Pyzd15wAdSJswvs5CJzJNV+qqSUcr/aCD+jpQSBtXvGnV0pnrjoc8zFTe9fcKCatkpFpOO7yAzpO998HA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.27.2': + resolution: {integrity: sha512-xsPeJgh2ThBpUqlLgRfiVYBEf/P1nWlWvReG+aBWfNv3XEBpa6ZCmxSVnxJgLgkNz4IbxpLy64h2gCmAAQLneQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.27.2': + resolution: {integrity: sha512-KnXU4m9MywuZFedL35Z3PuwiTSn/yqRIhrEA9j+7OSkji39NzVkgxuxTYg5F8ryGysq4iFADaU5osSizMXhU2A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.27.2': + resolution: {integrity: sha512-Hj77A3yTvUeCIx/Vi+4d4IbYhyTwtHj07lVzUgpUq9YpJSEiGJj4vXMKwzJ3w5zp5v3PFvpJNgc/J31smZey6g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.27.2': + resolution: {integrity: sha512-RjgKf5C3xbn8gxvCm5VgKZ4nn0pRAIe90J0/fdHUsgztd3+Zesb2lm2+r6uX4prV2eUByuxJNdt647/1KPRq5g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.27.2': + resolution: {integrity: sha512-duq21FoXwQtuws+V9H6UZ+eCBc7fxSpMK1GQINKn3fAyd9DFYKPJNcUhdIKOrMFjLEJgQskoMoiuizMt+dl20g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.27.2': + resolution: {integrity: sha512-6npqOKEPRZkLrMcvyC/32OzJ2srdPzCylJjiTJT2c0bwwSGm7nz2F9mNQ1WrAqCBZROcQn91Fno+khFhVijmFA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.27.2': + resolution: {integrity: sha512-V9Xg6eXtgBtHq2jnuQwM/jr2mwe2EycnopO8cbOvpzFuySCGtKlPCI3Hj9xup/pJK5Q0388qfZZy2DqV2J8ftw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.27.2': + resolution: {integrity: sha512-uCFX9gtZJoQl2xDTpRdseYuNqyKkuMDtH6zSrBTA28yTfKyjN9hQ2B04N5ynR8ILCoSDOrG/Eg+J2TtJ1e/CSA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.27.2': + resolution: {integrity: sha512-/PU9P+7Rkz8JFYDHIi+xzHabOu9qEWR07L5nWLIUsvserrxegZExKCi2jhMZRd0ATdboKylu/K5yAXbp7fYFvA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.27.2': + resolution: {integrity: sha512-eCHmol/dT5odMYi/N0R0HC8V8QE40rEpkyje/ZAXJYNNoSfrObOvG/Mn+s1F/FJyB7co7UQZZf6FuWnN6a7f4g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.27.2': + resolution: {integrity: sha512-DEP3Njr9/ADDln3kNi76PXonLMSSMiCir0VHXxmGSHxCxDfQ70oWjHcJGfiBugzaqmYdTC7Y+8Int6qbnxPBIQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.27.2': + resolution: {integrity: sha512-NHGo5i6IE/PtEPh5m0yw5OmPMpesFnzMIS/lzvN5vknnC1sXM5Z/id5VgcNPgpD+wHmIcuYYgW+Q53v+9s96lQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.27.2': + resolution: {integrity: sha512-PaW2DY5Tan+IFvNJGHDmUrORadbe/Ceh8tQxi8cmdQVCCYsLoQo2cuaSj+AU+YRX8M4ivS2vJ9UGaxfuNN7gmg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.27.2': + resolution: {integrity: sha512-dOlWEMg2gI91Qx5I/HYqOD6iqlJspxLcS4Zlg3vjk1srE67z5T2Uz91yg/qA8sY0XcwQrFzWWiZhMNERylLrpQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.27.2': + resolution: {integrity: sha512-euMIv/4x5Y2/ImlbGl88mwKNXDsvzbWUlT7DFky76z2keajCtcbAsN9LUdmk31hAoVmJJYSThgdA0EsPeTr1+w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.27.2': + resolution: {integrity: sha512-RsnE6LQkUHlkC10RKngtHNLxb7scFykEbEwOFDjr3CeCMG+Rr+cKqlkKc2/wJ1u4u990urRHCbjz31x84PBrSQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.27.2': + resolution: {integrity: sha512-foJM5vv+z2KQmn7emYdDLyTbkoO5bkHZE1oth2tWbQNGW7mX32d46Hz6T0MqXdWS2vBZhaEtHqdy9WYwGfiliA==} + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.9.1': + resolution: {integrity: sha512-2/ncHSCdAh5OHem1fMITrWEzzl97OdMK1PHc9CkxSJnphLjRubfxB5sbc5tDhcO68a5tVy+DxwaBgDec3PXnOg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.9.1': + resolution: {integrity: sha512-4MDOFC5zmNqRJ9RGFOH95oYf27J9HniLVpB1pYm2gGeNHdl2QvDMtx2QTuMHQ6+OTn/3y1BHYuhBGp7d405oLA==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.9.1': + resolution: {integrity: sha512-eVW/BjRW8/HpLe3+1jRU7w7PdRLBgnEEYTkHJISU8805/EKT03xNZn6CfaBpKfeAloY4043hbGzE/NP9IahdpQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.9.1': + resolution: {integrity: sha512-8m3u1v8R8NgI/9+cHMkzk14w87blSy3OsQPWPfhOL+XPwhyLPvat+ahQJb2nZmltjTgkB4IbzKFSfbuA34LmNA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.9.1': + resolution: {integrity: sha512-hpT0sQAZnW8l02I289yeyFfT9llGO9PzKDxUq8pocKtioEHiElRqR53juCWoSmzuWi+6KX7zUJ0NKCBrc8pmDg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.9.1': + resolution: {integrity: sha512-sGFdpdAYusk/ropHiwtXom2JrdaKPxl8MqemRv6dvxZq1Gm/GdmOowxdXIPjCgBGMgoXVcgNviH6CgiO5q+UtA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.9.1': + resolution: {integrity: sha512-YtNLNwIWs0Z2+XgBs6+LrCIGtfCDtNr4S4b6Q5HDOreEIGzSvhkef8eyBI5L+fJ2eGov4b7iEo61C4izDJS5RA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.9.1': + resolution: {integrity: sha512-qSxD3uZW2vSiHqUt30vUi0PB92zDh9bjqh5YKpfhhVa7h1vt/xXhlid8yMvSNToTfzhRrTEffOAPUr7WVoyQUA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.9.1': + resolution: {integrity: sha512-C3fPEwyX/WRPlX6zIToNykJuz1JkZX0sk8H1QH2vpnKuySUkt/Ur5K2FzLgSWzJdbfxstpgS151/es0VGAD+ZA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.9.1': + resolution: {integrity: sha512-2XZ+U1AyVsOAXeH6WK1syDm7+gwTjA8fShs93WcbxnK7HV+NigDlvr4124CeJLTHyh3fMh1o7+CnQnaBJhlysQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.9.1': + resolution: {integrity: sha512-OnPc+Kt5oy3xTvr/KCUOqE9ptJcWbyQgAUr1ydh9EmbBcmJTaO1kfQCxm/axzJi6sKeDTxL9rX5zvLOhoYIaQw==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.14': + resolution: {integrity: sha512-PbSmTiYCN+GMrvfjrMo9bdY+f2COnwbdnoMw7rqU/PI5jXpKjxOGZ0qqZCImxnT81NkNsKnmEpvu+hRXLBeCJg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/node@20.17.6': + resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + + '@types/react-transition-group@4.4.11': + resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} + + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + + '@types/stats.js@0.17.3': + resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==} + + '@types/three@0.156.0': + resolution: {integrity: sha512-733bXDSRdlrxqOmQuOmfC1UBRuJ2pREPk8sWnx9MtIJEVDQMx8U0NQO5MVVaOrjzDPyLI+cFPim2X/ss9v0+LQ==} + + '@types/webxr@0.5.20': + resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} + + '@vitejs/plugin-legacy@5.4.3': + resolution: {integrity: sha512-wsyXK9mascyplcqvww1gA1xYiy29iRHfyciw+a0t7qRNdzX6PdfSWmOoCi74epr87DujM+5J+rnnSv+4PazqVg==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + terser: ^5.4.0 + vite: ^5.0.0 + + '@vitejs/plugin-react-swc@3.7.1': + resolution: {integrity: sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==} + peerDependencies: + vite: ^4 || ^5 + + '@vitejs/plugin-react@4.3.3': + resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + ahooks@3.8.1: + resolution: {integrity: sha512-JoP9+/RWO7MnI/uSKdvQ8WB10Y3oo1PjLv+4Sv4Vpm19Z86VUMdXh+RhWvMGxZZs06sq2p0xVtFk8Oh5ZObsoA==} + engines: {node: '>=8.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + babel-plugin-polyfill-corejs2@0.4.12: + resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.10.6: + resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.3: + resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + browserslist-to-esbuild@2.1.1: + resolution: {integrity: sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + browserslist: '*' + + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001680: + resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.39.0: + resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} + + core-js@3.39.0: + resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + electron-to-chromium@1.5.62: + resolution: {integrity: sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fflate@0.6.10: + resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + jotai@2.10.2: + resolution: {integrity: sha512-DqsBTlRglIBviuJLfK6JxZzpd6vKfbuJ4IqRCz70RFEDeZf46Fcteb/FXxNr1UnoxR5oUy3oq7IE8BrEq0G5DQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + meshoptimizer@0.18.1: + resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mui-file-input@6.0.0: + resolution: {integrity: sha512-obxuGvY2+SuafXUdKOq06jw/Ams3xPsYafkSb71wmnC2jz6k+DHeKomXlawqAUM7w6WbRy2J3mQ4yoRLZYMx3A==} + peerDependencies: + '@emotion/react': ^11.13.3 + '@emotion/styled': ^11.13.0 + '@mui/material': ^5.0.0 || ^6.0.0 + '@types/react': ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + pretty-bytes@6.1.1: + resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} + engines: {node: ^14.13.1 || >=16.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-router-dom@6.28.0: + resolution: {integrity: sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.28.0: + resolution: {integrity: sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + + regexpu-core@6.1.1: + resolution: {integrity: sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.11.2: + resolution: {integrity: sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==} + hasBin: true + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + rollup@4.27.2: + resolution: {integrity: sha512-KreA+PzWmk2yaFmZVwe6GB2uBD86nXl86OsDkt1bJS9p3vqWuEQ6HnJJ+j/mZi/q0920P99/MVRlB4L3crpF5w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + skinview-utils@0.7.1: + resolution: {integrity: sha512-4eLrMqR526ehlZbsd8SuZ/CHpS9GiH0xUMoV+PYlJVi95ZFz5HJu7Spt5XYa72DRS7wgt5qquvHZf0XZJgmu9Q==} + + skinview3d@3.1.0: + resolution: {integrity: sha512-L+HXXAP4qYjLcY3YHasXKie9KXQpv/mPTMxgLOEd+hVQRdQkPs5xdWaKuOmlZY8UnyZzecQM7yrWRzgT/e7HZw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + systemjs@6.15.1: + resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==} + + terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} + engines: {node: '>=10'} + hasBin: true + + three@0.156.1: + resolution: {integrity: sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ==} + + tilg@0.1.1: + resolution: {integrity: sha512-0uHyTAUM0tJL792LeviRPFkJtCbF6Za3/hbbnRmWGUaicOhbJ0IpvBViXiXTF7nk6R0L6vve2XLesQzn5jEVng==} + peerDependencies: + react: ^18.0.0 || ^17.0.0 + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.0: + resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.2': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-annotate-as-pure@7.25.9': + dependencies: + '@babel/types': 7.26.0 + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-compilation-targets@7.25.9': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.1.1 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + debug: 4.3.7 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.25.9': + dependencies: + '@babel/types': 7.26.0 + + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-simple-access@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helper-wrap-function@7.25.9': + dependencies: + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/template': 7.25.9 + + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-simple-access': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/preset-env@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.0) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.0) + core-js-compat: 3.39.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/types': 7.26.0 + esutils: 2.0.3 + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@emotion/babel-plugin@11.12.0': + dependencies: + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.26.0 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.2 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.13.1': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.1 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.3.1': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/cache': 11.13.1 + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) + '@emotion/utils': 1.4.1 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.2': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.1 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) + '@emotion/utils': 1.4.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.4.1': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@marsidev/react-turnstile@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@mui/core-downloads-tracker@6.1.7': {} + + '@mui/icons-material@6.1.7(@mui/material@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@mui/material': 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@mui/material@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@mui/core-downloads-tracker': 6.1.7 + '@mui/system': 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@mui/types': 7.2.19(@types/react@18.3.12) + '@mui/utils': 6.1.7(@types/react@18.3.12)(react@18.3.1) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.11 + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@types/react': 18.3.12 + + '@mui/private-theming@6.1.7(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@mui/utils': 6.1.7(@types/react@18.3.12)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@mui/styled-engine@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/cache': 11.13.1 + '@emotion/serialize': 1.3.2 + '@emotion/sheet': 1.4.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + + '@mui/system@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@mui/private-theming': 6.1.7(@types/react@18.3.12)(react@18.3.1) + '@mui/styled-engine': 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.19(@types/react@18.3.12) + '@mui/utils': 6.1.7(@types/react@18.3.12)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@types/react': 18.3.12 + + '@mui/types@7.2.19(@types/react@18.3.12)': + optionalDependencies: + '@types/react': 18.3.12 + + '@mui/utils@6.1.7(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@mui/types': 7.2.19(@types/react@18.3.12) + '@types/prop-types': 15.7.13 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@popperjs/core@2.11.8': {} + + '@remix-run/router@1.21.0': {} + + '@rollup/rollup-android-arm-eabi@4.27.2': + optional: true + + '@rollup/rollup-android-arm64@4.27.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.27.2': + optional: true + + '@rollup/rollup-darwin-x64@4.27.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.27.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.27.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.27.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.27.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.27.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.27.2': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.27.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.27.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.27.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.27.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.27.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.27.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.27.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.27.2': + optional: true + + '@swc/core-darwin-arm64@1.9.1': + optional: true + + '@swc/core-darwin-x64@1.9.1': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.9.1': + optional: true + + '@swc/core-linux-arm64-gnu@1.9.1': + optional: true + + '@swc/core-linux-arm64-musl@1.9.1': + optional: true + + '@swc/core-linux-x64-gnu@1.9.1': + optional: true + + '@swc/core-linux-x64-musl@1.9.1': + optional: true + + '@swc/core-win32-arm64-msvc@1.9.1': + optional: true + + '@swc/core-win32-ia32-msvc@1.9.1': + optional: true + + '@swc/core-win32-x64-msvc@1.9.1': + optional: true + + '@swc/core@1.9.1': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.14 + optionalDependencies: + '@swc/core-darwin-arm64': 1.9.1 + '@swc/core-darwin-x64': 1.9.1 + '@swc/core-linux-arm-gnueabihf': 1.9.1 + '@swc/core-linux-arm64-gnu': 1.9.1 + '@swc/core-linux-arm64-musl': 1.9.1 + '@swc/core-linux-x64-gnu': 1.9.1 + '@swc/core-linux-x64-musl': 1.9.1 + '@swc/core-win32-arm64-msvc': 1.9.1 + '@swc/core-win32-ia32-msvc': 1.9.1 + '@swc/core-win32-x64-msvc': 1.9.1 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.14': + dependencies: + '@swc/counter': 0.1.3 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.0 + + '@types/estree@1.0.6': {} + + '@types/node@20.17.6': + dependencies: + undici-types: 6.19.8 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.1': + dependencies: + '@types/react': 18.3.12 + + '@types/react-transition-group@4.4.11': + dependencies: + '@types/react': 18.3.12 + + '@types/react@18.3.12': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@types/stats.js@0.17.3': {} + + '@types/three@0.156.0': + dependencies: + '@types/stats.js': 0.17.3 + '@types/webxr': 0.5.20 + fflate: 0.6.10 + meshoptimizer: 0.18.1 + + '@types/webxr@0.5.20': {} + + '@vitejs/plugin-legacy@5.4.3(terser@5.36.0)(vite@5.4.11(@types/node@20.17.6)(terser@5.36.0))': + dependencies: + '@babel/core': 7.26.0 + '@babel/preset-env': 7.26.0(@babel/core@7.26.0) + browserslist: 4.24.2 + browserslist-to-esbuild: 2.1.1(browserslist@4.24.2) + core-js: 3.39.0 + magic-string: 0.30.12 + regenerator-runtime: 0.14.1 + systemjs: 6.15.1 + terser: 5.36.0 + vite: 5.4.11(@types/node@20.17.6)(terser@5.36.0) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react-swc@3.7.1(vite@5.4.11(@types/node@20.17.6)(terser@5.36.0))': + dependencies: + '@swc/core': 1.9.1 + vite: 5.4.11(@types/node@20.17.6)(terser@5.36.0) + transitivePeerDependencies: + - '@swc/helpers' + + '@vitejs/plugin-react@4.3.3(vite@5.4.11(@types/node@20.17.6)(terser@5.36.0))': + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.11(@types/node@20.17.6)(terser@5.36.0) + transitivePeerDependencies: + - supports-color + + acorn@8.14.0: {} + + ahooks@3.8.1(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + dayjs: 1.11.13 + intersection-observer: 0.12.2 + js-cookie: 3.0.5 + lodash: 4.17.21 + react: 18.3.1 + react-fast-compare: 3.2.2 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.8.1 + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.26.0 + cosmiconfig: 7.1.0 + resolve: 1.22.8 + + babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.0): + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + core-js-compat: 3.39.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + browserslist-to-esbuild@2.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + meow: 13.2.0 + + browserslist@4.24.2: + dependencies: + caniuse-lite: 1.0.30001680 + electron-to-chromium: 1.5.62 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + + buffer-from@1.1.2: {} + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001680: {} + + clsx@2.1.1: {} + + commander@2.20.3: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.39.0: + dependencies: + browserslist: 4.24.2 + + core-js@3.39.0: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + csstype@3.1.3: {} + + dayjs@1.11.13: {} + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.26.0 + csstype: 3.1.3 + + electron-to-chromium@1.5.62: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + esutils@2.0.3: {} + + fflate@0.6.10: {} + + find-root@1.1.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + globals@11.12.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + immer@10.1.1: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + intersection-observer@0.12.2: {} + + is-arrayish@0.2.1: {} + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + jotai@2.10.2(@types/react@18.3.12)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.12 + react: 18.3.1 + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + jsesc@3.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json5@2.2.3: {} + + lines-and-columns@1.2.4: {} + + lodash.debounce@4.0.8: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.12: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + meow@13.2.0: {} + + meshoptimizer@0.18.1: {} + + ms@2.1.3: {} + + mui-file-input@6.0.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/material@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@mui/material': 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + pretty-bytes: 6.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + + nanoid@3.3.7: {} + + node-releases@2.0.18: {} + + object-assign@4.1.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-bytes@6.1.1: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-fast-compare@3.2.2: {} + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-refresh@0.14.2: {} + + react-router-dom@6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.21.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.28.0(react@18.3.1) + + react-router@6.28.0(react@18.3.1): + dependencies: + '@remix-run/router': 1.21.0 + react: 18.3.1 + + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regenerator-transform@0.15.2: + dependencies: + '@babel/runtime': 7.26.0 + + regexpu-core@6.1.1: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.11.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.0 + + regjsgen@0.8.0: {} + + regjsparser@0.11.2: + dependencies: + jsesc: 3.0.2 + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.27.2: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.27.2 + '@rollup/rollup-android-arm64': 4.27.2 + '@rollup/rollup-darwin-arm64': 4.27.2 + '@rollup/rollup-darwin-x64': 4.27.2 + '@rollup/rollup-freebsd-arm64': 4.27.2 + '@rollup/rollup-freebsd-x64': 4.27.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.27.2 + '@rollup/rollup-linux-arm-musleabihf': 4.27.2 + '@rollup/rollup-linux-arm64-gnu': 4.27.2 + '@rollup/rollup-linux-arm64-musl': 4.27.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.27.2 + '@rollup/rollup-linux-riscv64-gnu': 4.27.2 + '@rollup/rollup-linux-s390x-gnu': 4.27.2 + '@rollup/rollup-linux-x64-gnu': 4.27.2 + '@rollup/rollup-linux-x64-musl': 4.27.2 + '@rollup/rollup-win32-arm64-msvc': 4.27.2 + '@rollup/rollup-win32-ia32-msvc': 4.27.2 + '@rollup/rollup-win32-x64-msvc': 4.27.2 + fsevents: 2.3.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + screenfull@5.2.0: {} + + semver@6.3.1: {} + + skinview-utils@0.7.1: {} + + skinview3d@3.1.0: + dependencies: + '@types/three': 0.156.0 + skinview-utils: 0.7.1 + three: 0.156.1 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + stylis@4.2.0: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + systemjs@6.15.1: {} + + terser@5.36.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + three@0.156.1: {} + + tilg@0.1.1(react@18.3.1): + dependencies: + react: 18.3.1 + + tslib@2.8.1: {} + + typescript@5.6.3: {} + + undici-types@6.19.8: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.2.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + vite@5.4.11(@types/node@20.17.6)(terser@5.36.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.27.2 + optionalDependencies: + '@types/node': 20.17.6 + fsevents: 2.3.3 + terser: 5.36.0 + + yallist@3.1.1: {} + + yaml@1.10.2: {} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..b25320c --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,13 @@ +import { PageRoute } from '@/Route' + +function App() { + + return ( + <> + + + ) +} + + +export default App diff --git a/frontend/src/Route.tsx b/frontend/src/Route.tsx new file mode 100644 index 0000000..83a2f18 --- /dev/null +++ b/frontend/src/Route.tsx @@ -0,0 +1,58 @@ +import { Routes, Route, createBrowserRouter, RouterProvider, Outlet } from "react-router-dom"; +import { ScrollRestoration } from "react-router-dom"; +import Login from '@/views/Login' +import Register from '@/views/Register' +import Profile from '@/views/profile/Profile' +import Textures from '@/views/profile/Textures' +import Security from '@/views/profile/Security' +import Layout from '@/views/Layout' +import UserAdmin from "@/views/admin/UserAdmin"; +import NeedLogin from "@/components/NeedLogin"; +import Index from "@/views/Index"; +import SendEmail from "@/views/SendEmail"; +import { sendForgotEmail, sendRegEmail } from "@/apis/apis"; +import Forgot from "@/views/Forgot"; + +const router = createBrowserRouter([ + { path: "*", Component: Root }, +]) + +function Root() { + return ( + <> + + }> + } /> + 404

} /> + } /> + } /> + } /> + } /> + } /> + + }> + } /> + } /> + } /> + + + }> + } /> + + + +
+ + + ) +} + + +export function PageRoute() { + return ( + <> + + + ) +} + diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts new file mode 100644 index 0000000..b996595 --- /dev/null +++ b/frontend/src/apis/apis.ts @@ -0,0 +1,156 @@ +import type { tokenData, ApiUser, YggProfile, ApiConfig, List, UserInfo, EditUser } from '@/apis/model' +import { apiGet } from '@/apis/utils' +import root from '@/utils/root' + +export async function login(email: string, password: string, captchaToken: string) { + const v = await fetch(root() + "/api/v1/user/login", { + method: "POST", + body: JSON.stringify({ + "email": email, + "password": password, + "CaptchaToken": captchaToken + }) + }) + return await apiGet(v) +} + +export async function register(email: string, username: string, password: string, captchaToken: string, code: string) { + const v = await fetch(root() + "/api/v1/user/reg", { + method: "POST", + body: JSON.stringify({ + "Email": email, + "Password": password, + "Name": username, + "CaptchaToken": captchaToken, + "EmailJwt": code, + }) + }) + return await apiGet(v) +} + +export async function userInfo(token: string) { + if (token == "") return + const v = await fetch(root() + "/api/v1/user", { + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(v) +} + +export async function yggProfile(uuid: string) { + if (uuid == "") return + const v = await fetch(root() + "/api/yggdrasil/sessionserver/session/minecraft/profile/" + uuid) + const data = await v.json() + if (!v.ok) { + throw new Error(data?.errorMessage) + } + return data as YggProfile +} + +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/v1/user/skin/" + textureType, { + method: "PUT", + body: f, + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(r) +} + +export async function changePasswd(old: string, newpa: string, token: string) { + const r = await fetch(root() + "/api/v1/user/password", { + method: "POST", + body: JSON.stringify({ + "old": old, + "new": newpa + }), + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(r) +} + +export async function getConfig() { + const r = await fetch(root() + "/api/v1/config") + return await apiGet(r) +} + +export async function changeName(name: string, token: string) { + const r = await fetch(root() + "/api/v1/user/name", { + method: "POST", + body: JSON.stringify({ + "name": name, + }), + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(r) +} + +export async function ListUser(page: number, token: string, email: string, name: string) { + const u = new URL(root() + "/api/v1/admin/users") + u.searchParams.set("page", String(page)) + u.searchParams.set("email", email) + u.searchParams.set("name", name) + const r = await fetch(u.toString(), { + method: "GET", + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet>(r) +} + +export async function editUser(u: EditUser, token: string, uid: string) { + const r = await fetch(root() + "/api/v1/admin/user/" + uid, { + method: "PATCH", + headers: { + "Authorization": "Bearer " + token + }, + body: JSON.stringify(u) + }) + return await apiGet(r) +} + +export async function sendRegEmail(email: string, captchaToken: string) { + const r = await fetch(root() + "/api/v1/user/reg_email", { + method: "POST", + body: JSON.stringify({ + "email": email, + "captchaToken": captchaToken + }) + }) + return await apiGet(r) +} + +export async function sendForgotEmail(email: string, captchaToken: string) { + const r = await fetch(root() + "/api/v1/user/forgot_email", { + method: "POST", + body: JSON.stringify({ + "email": email, + "captchaToken": captchaToken + }) + }) + return await apiGet(r) +} + + +export async function forgotPassWord(email: string, emailJwt: string, password: string) { + const r = await fetch(root() + "/api/v1/user/forgot", { + method: "POST", + body: JSON.stringify({ + "email": email, + "emailJwt": emailJwt, + "passWord": password, + }) + }) + return await apiGet(r) +} \ No newline at end of file diff --git a/frontend/src/apis/error.ts b/frontend/src/apis/error.ts new file mode 100644 index 0000000..58c962b --- /dev/null +++ b/frontend/src/apis/error.ts @@ -0,0 +1,8 @@ +export class ApiErr extends Error { + readonly code: number + + constructor(code: number, msg: string) { + super(msg) + this.code = code + } +} \ No newline at end of file diff --git a/frontend/src/apis/model.ts b/frontend/src/apis/model.ts new file mode 100644 index 0000000..5dc0828 --- /dev/null +++ b/frontend/src/apis/model.ts @@ -0,0 +1,66 @@ +export interface tokenData { + token: string + name: string + uuid: string +} + +export interface Api { + code: number + msg: string + data: T +} + +export interface List { + total: number + list: T[] +} + + +interface captcha { + type: string + siteKey: string +} + + +export interface ApiUser { + uid: string + uuid: string + is_admin: boolean +} + +export interface YggProfile { + name: string + properties: { + name: string + value: string + }[] +} + +export interface ApiConfig { + captcha: captcha + AllowChangeName: boolean + serverName: string + NeedEmail: boolean + AllowDomain: string[] + EmailReg: string + EmailRegMsg: string +} + +export interface UserInfo { + uid: number + uuid: string + is_admin: boolean + is_disable: boolean + email: string + reg_ip: string + name: string +} + +export interface EditUser { + email?: string + name?: string + password?: string + is_admin?: boolean + is_disable?: boolean + del_textures?: boolean +} \ No newline at end of file diff --git a/frontend/src/apis/utils.ts b/frontend/src/apis/utils.ts new file mode 100644 index 0000000..be66411 --- /dev/null +++ b/frontend/src/apis/utils.ts @@ -0,0 +1,10 @@ +import { ApiErr } from "./error" + +export async function apiGet(v: Response) { + type api = { data: T, msg: string, code: number } + const data = await v.json() as api + if (!v.ok) { + throw new ApiErr(data.code, data.msg) + } + return data.data +} diff --git a/frontend/src/components/CaptchaWidget.tsx b/frontend/src/components/CaptchaWidget.tsx new file mode 100644 index 0000000..c24cfcc --- /dev/null +++ b/frontend/src/components/CaptchaWidget.tsx @@ -0,0 +1,65 @@ +import { Turnstile } from '@marsidev/react-turnstile' +import Button from '@mui/material/Button' +import { useRef, useState, memo, forwardRef, useImperativeHandle, useEffect } from 'react' +import type { TurnstileInstance } from '@marsidev/react-turnstile' +import Alert from '@mui/material/Alert'; +import Skeleton from '@mui/material/Skeleton'; +import { useRequest } from 'ahooks'; +import { getConfig } from '@/apis/apis'; + +interface prop { + onSuccess: ((token: string) => void) +} + +export type refType = { + reload: () => void +} + + +const CaptchaWidget = forwardRef(({ onSuccess }, ref) => { + const Turnstileref = useRef(null) + const [key, setKey] = useState(1) + const { data, error, loading } = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 600000, + loadingDelay: 200 + }) + + useImperativeHandle(ref, () => { + return { + reload: () => { + setKey(key + 1) + } + } + }) + useEffect(() => { + if (data?.captcha?.type != "turnstile") { + onSuccess("ok") + return + } + }, [data?.captcha?.type, onSuccess]) + + + if (error) { + console.warn(error) + return {String(error)} + } + if (!data && loading) { + return + } + + if (data?.captcha.type == "") { + return <> + } + + return ( + <> + + + + ) +}) + +const CaptchaWidgetMemo = memo(CaptchaWidget) + +export default CaptchaWidgetMemo \ No newline at end of file diff --git a/frontend/src/components/CheckInput.tsx b/frontend/src/components/CheckInput.tsx new file mode 100644 index 0000000..e323714 --- /dev/null +++ b/frontend/src/components/CheckInput.tsx @@ -0,0 +1,65 @@ +import TextField from '@mui/material/TextField'; +import { useState, useImperativeHandle, forwardRef } from 'react'; +import type { TextFieldProps } from '@mui/material/TextField'; +import { useControllableValue } from 'ahooks'; + +export type refType = { + verify: () => boolean +} + +type prop = { + checkList: { + errMsg: string + reg: RegExp + }[] +} & Omit, 'helperText'> + +export const CheckInput = forwardRef(({ required, checkList, ...textFied }, ref) => { + const [err, setErr] = useState(""); + const [value, setValue] = useControllableValue(textFied); + + + const check = (value: string) => { + if (required && (!value || value == "")) { + setErr("此项必填") + return false + } + for (const v of checkList) { + if (!v.reg.test(value)) { + setErr(v.errMsg) + return false + } + } + setErr("") + return true + } + + const verify = () => { + return check(value) + } + + useImperativeHandle(ref, () => { + return { + verify + } + }) + + const onChange = (event: React.ChangeEvent) => { + const value = event.target.value + setValue(value) + check(value) + } + + + + return +}) + +export default CheckInput \ No newline at end of file diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx new file mode 100644 index 0000000..9b09deb --- /dev/null +++ b/frontend/src/components/Loading.tsx @@ -0,0 +1,15 @@ +import Backdrop from "@mui/material/Backdrop"; +import CircularProgress from "@mui/material/CircularProgress"; + +export default function Loading() { + return ( + <> + theme.zIndex.modal + 1 }} + open={true} + > + + + + ) +} diff --git a/frontend/src/components/NeedLogin.tsx b/frontend/src/components/NeedLogin.tsx new file mode 100644 index 0000000..b7b38b9 --- /dev/null +++ b/frontend/src/components/NeedLogin.tsx @@ -0,0 +1,41 @@ +import { userInfo } from "@/apis/apis"; +import { ApiErr } from "@/apis/error"; +import { token } from "@/store/store"; +import { useRequest } from "ahooks"; +import { useAtomValue } from "jotai"; +import { useEffect } from "react"; +import { useNavigate, Navigate } from "react-router-dom"; + + +export default function NeedLogin({ children, needAdmin = false }: { children: JSX.Element, needAdmin?: boolean }) { + const t = useAtomValue(token) + const navigate = useNavigate(); + const u = useRequest(() => userInfo(t), { + refreshDeps: [t], + cacheKey: "/api/v1/user" + t, + staleTime: 60000, + onError: e => { + if (e instanceof ApiErr && e.code == 5) { + navigate("/login") + } + console.warn(e) + } + }) + + useEffect(() => { + if (!u.data) return + if (!u.data.is_admin && needAdmin) { + navigate("/login") + } + if (u.data.uuid == "") { + navigate("/login") + } + }, [navigate, needAdmin, u.data]) + + if (!localStorage.getItem("token") || localStorage.getItem("token") == '""') { + return + } + + + return <> {children} +} \ No newline at end of file diff --git a/frontend/src/components/SkinViewUUID.tsx b/frontend/src/components/SkinViewUUID.tsx new file mode 100644 index 0000000..d68b300 --- /dev/null +++ b/frontend/src/components/SkinViewUUID.tsx @@ -0,0 +1,89 @@ +import { yggProfile } from "@/apis/apis"; +import { decodeSkin } from "@/utils/skin"; +import Skeleton from "@mui/material/Skeleton"; +import { useHover, useMemoizedFn, useRequest, useUnmount } from "ahooks"; +import { memo, useEffect, useRef, useState } from "react"; +import ReactSkinview3d, { ReactSkinview3dOptions } from "@/components/Skinview3d"; +import { SkinViewer, WalkingAnimation } from "skinview3d"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +interface prop { + uuid: string + width: number + height: number +} + +const SkinViewUUID = memo(function SkinViewUUID({ uuid, width, height }: prop) { + const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" }) + const [err, setErr] = useState("") + + const SkinInfo = useRequest(() => yggProfile(uuid), { + cacheKey: "/api/yggdrasil/sessionserver/session/minecraft/profile/" + uuid, + onError: e => { + console.warn(e) + setErr(String(e)) + }, + refreshDeps: [uuid], + }) + + useEffect(() => { + if (!SkinInfo.data) return + const [skin, cape, model] = decodeSkin(SkinInfo.data) + setTextures({ cape: cape, skin: skin, model: model }) + }, [SkinInfo.data]) + + if (err != "") { + return {err} + } + return (<> + { + (SkinInfo.loading && !SkinInfo.data) ? + : (textures.skin != "" || textures.cape != "") ? ( + ) : + 还没有设置皮肤 + + } + ) +}) + + +const MySkin = function MySkin(p: ReactSkinview3dOptions) { + const refSkinview3d = useRef(null); + const skinisHovering = useHover(refSkinview3d); + const skinview3dView = useRef(null); + + useEffect(() => { + if (skinview3dView.current) { + skinview3dView.current.autoRotate = !skinisHovering + } + if (skinview3dView.current?.animation) { + skinview3dView.current.animation.paused = skinisHovering + } + }, [skinisHovering]) + + useUnmount(() => { + skinview3dView.current?.dispose() + }) + + const handelOnReady = useMemoizedFn(v => { + v.viewer.animation = new WalkingAnimation() + v.viewer.autoRotate = true + skinview3dView.current = v.viewer + }) + + return
+ +
+} + +export default SkinViewUUID \ No newline at end of file diff --git a/frontend/src/components/Skinview3d.tsx b/frontend/src/components/Skinview3d.tsx new file mode 100644 index 0000000..b4fdcc8 --- /dev/null +++ b/frontend/src/components/Skinview3d.tsx @@ -0,0 +1,101 @@ +import { memo, useEffect, useRef } from "react"; +import { SkinViewer, SkinViewerOptions } from "skinview3d"; + +// https://github.com/Hacksore/react-skinview3d/blob/master/src/index.tsx + +/** + * This is the interface that describes the parameter in `onReady` + */ +export interface ViewerReadyCallbackOptions { + /** + * The instance of the skinview3d + */ + viewer: SkinViewer; + /** + * The ref to the canvas element + */ + canvasRef: HTMLCanvasElement; +} + +export interface ReactSkinview3dOptions { + /** + * The class names to apply to the canvas + */ + className?: string; + /** + * The width of the canvas + */ + width: number | string; + /** + * The height of the canvas + */ + height: number | string; + /** + * The skin to load in the canvas + */ + skinUrl: string; + /** + * The cape to load in the canvas + */ + capeUrl?: string; + /** + * A function that is called when the skin viewer is ready + * @param {SkinViewer} instance callback function to execute when the viewer is loaded {@link SkinViewer} + * @example + * onReady((instance) => { + * console.log(instance) + * }) + */ + onReady?: ({ viewer, canvasRef }: ViewerReadyCallbackOptions) => void; + /** + * Parameters passed to the skinview3d constructor allowing you to override or add extra features + * @notes please take a look at the upstream repo for more info + * [bs-community/skinview3d](https://bs-community.github.io/skinview3d/) + */ + options?: SkinViewerOptions; +} + +/** + * A skinview3d component + */ +const ReactSkinview3d = memo(function ReactSkinview3d({ + className, + width, + height, + skinUrl, + capeUrl, + onReady, + options, +}: ReactSkinview3dOptions) { + const canvasRef = useRef(null); + const skinviewRef = useRef(); + + useEffect(() => { + if (!canvasRef.current) return + + const viewer = new SkinViewer({ + canvas: canvasRef.current, + width: Number(width), + height: Number(height), + ...options, + }); + + // handle cape/skin load initially + skinUrl && viewer.loadSkin(skinUrl, { model: options?.model ?? "auto-detect" }); + capeUrl && viewer.loadCape(capeUrl); + + skinviewRef.current = viewer; + + // call onReady with the viewer instance + if (onReady) { + onReady({ viewer: skinviewRef.current, canvasRef: canvasRef.current }); + } + + return () => viewer.dispose() + }, [capeUrl, height, onReady, options, skinUrl, width]); + + + return ; +}) + +export default ReactSkinview3d; \ No newline at end of file diff --git a/frontend/src/hooks/useTitle.ts b/frontend/src/hooks/useTitle.ts new file mode 100644 index 0000000..2ec60da --- /dev/null +++ b/frontend/src/hooks/useTitle.ts @@ -0,0 +1,16 @@ +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(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + }) + useEffect(() => { + error && console.warn(error) + }, [error]) + auseTitle(title + " - " + data?.serverName, { + restoreOnUnmount: true + }) +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..30c9a76 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import CssBaseline from '@mui/material/CssBaseline'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + +) diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts new file mode 100644 index 0000000..00bc090 --- /dev/null +++ b/frontend/src/store/store.ts @@ -0,0 +1,10 @@ +import { atom } from 'jotai' +import { atomWithStorage } from 'jotai/utils' + +export const token = atomWithStorage("token", "") +export const user = atomWithStorage("username", { + name: "", + uuid: "" +}) + +export const LayoutAlertErr = atom("") \ No newline at end of file diff --git a/frontend/src/utils/email.ts b/frontend/src/utils/email.ts new file mode 100644 index 0000000..2d88b7d --- /dev/null +++ b/frontend/src/utils/email.ts @@ -0,0 +1,3 @@ +export function checkEmail(email: string) { + return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email); +} \ No newline at end of file diff --git a/frontend/src/utils/root.ts b/frontend/src/utils/root.ts new file mode 100644 index 0000000..d5d67a1 --- /dev/null +++ b/frontend/src/utils/root.ts @@ -0,0 +1,6 @@ +export default function root() { + if (import.meta.env.VITE_APIADDR != "") { + return import.meta.env.VITE_APIADDR + } + return location.origin +} \ No newline at end of file diff --git a/frontend/src/utils/skin.ts b/frontend/src/utils/skin.ts new file mode 100644 index 0000000..53b9295 --- /dev/null +++ b/frontend/src/utils/skin.ts @@ -0,0 +1,17 @@ +import { YggProfile } from "@/apis/model"; + +export function decodeSkin(y: YggProfile) { + if (y.properties.length == 0) { + return ["", "", ""] + } + const p = y.properties.find(v => v.name == "textures") + if (!p?.value || p?.value == "") { + return ["", "", ""] + } + const textures = JSON.parse(atob(p.value)) + + const skin = textures?.textures?.SKIN?.url as string ?? "" + const cape = textures?.textures?.CAPE?.url as string ?? "" + const model = textures?.textures?.SKIN?.metadata?.model as string ?? "default" + return [skin, cape, model] +} \ No newline at end of file diff --git a/frontend/src/views/Forgot.tsx b/frontend/src/views/Forgot.tsx new file mode 100644 index 0000000..65ad9ec --- /dev/null +++ b/frontend/src/views/Forgot.tsx @@ -0,0 +1,120 @@ +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import TextField from '@mui/material/TextField'; +import { useTitle } from 'ahooks'; +import { useEffect, useState } from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import Loading from '@/components/Loading'; +import { produce } from 'immer'; +import { forgotPassWord } from '@/apis/apis'; +import { useNavigate } from 'react-router-dom'; + +export default function Forgot() { + const [err, setErr] = useState("") + useTitle("重设密码") + const [passerr, setPasserr] = useState("") + const [pass, setPass] = useState({ + pass1: "", + pass2: "", + }) + const [load, setLoad] = useState(false) + const [email, setEmail] = useState("") + const [code, setCode] = useState("") + const navigate = useNavigate(); + + useEffect(() => { + if (pass.pass1 != pass.pass2 && pass.pass2 != "") { + setPasserr("密码不相等") + return + } + setPasserr("") + }, [pass.pass1, pass.pass2]) + + const u = new URL(location.href) + + useEffect(() => { + setEmail(u.searchParams.get("email") ?? "") + setCode(u.searchParams.get("code") ?? "") + }, [u.searchParams]) + + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + setLoad(true) + forgotPassWord(email, code, pass.pass1).then(() => { + navigate("/") + }).catch(e => { + setErr(String(e)) + }).finally(() => { setLoad(false) }) + } + + + return ( + + + + + + + + 找回密码 + + + + + setPass(produce(v => { v.pass1 = p.target.value }))} + autoComplete="new-password" + /> + + + setPass(produce(v => { v.pass2 = p.target.value }))} + autoComplete="new-password" + /> + + + + + + + setErr("")} severity="error">{err} + + + {load && } + + ) +} \ No newline at end of file diff --git a/frontend/src/views/Index.tsx b/frontend/src/views/Index.tsx new file mode 100644 index 0000000..cc1cccc --- /dev/null +++ b/frontend/src/views/Index.tsx @@ -0,0 +1,9 @@ +import Profile from "@/views/profile/Profile" +import Login from "@/views/Login" + +export default function Index() { + if (localStorage.getItem("token") && localStorage.getItem("token") != '""') { + return + } + return +} \ No newline at end of file diff --git a/frontend/src/views/Layout.tsx b/frontend/src/views/Layout.tsx new file mode 100644 index 0000000..87555b8 --- /dev/null +++ b/frontend/src/views/Layout.tsx @@ -0,0 +1,291 @@ +import * as React from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Drawer from '@mui/material/Drawer'; +import Toolbar from '@mui/material/Toolbar'; +import List from '@mui/material/List'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; +import MenuIcon from '@mui/icons-material/Menu'; +import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import AppBar from '@mui/material/AppBar'; +import { Outlet } from 'react-router-dom'; +import { AccountCircle } from '@mui/icons-material'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import { LayoutAlertErr, token, user } from '@/store/store'; +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 { getConfig, userInfo } from '@/apis/apis' +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import { memo } from 'react'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import PersonIcon from '@mui/icons-material/Person'; +import SecurityIcon from '@mui/icons-material/Security'; +import SettingsIcon from '@mui/icons-material/Settings'; +import { Link } from "react-router-dom"; +import GroupIcon from '@mui/icons-material/Group'; +import { ApiErr } from '@/apis/error'; + +const drawerWidth = 240; +const DrawerOpen = atom(false) + +const DrawerHeader = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + padding: theme.spacing(0, 1), + // necessary for content to be below app bar + ...theme.mixins.toolbar, + justifyContent: 'flex-end', +})); + +interface ListItem { + icon: JSX.Element + title: string + link: string +} + +const Layout = memo(function Layout() { + const theme = useTheme(); + const [err, setErr] = useAtom(LayoutAlertErr) + + return (<> + + + + + + + setErr("")} severity="error">{err} + + + + + + + + + ) +}) + +const MyToolbar = memo(function MyToolbar() { + const [nowUser, setNowUser] = useAtom(user) + const [anchorEl, setAnchorEl] = React.useState(null); + const navigate = useNavigate(); + const setToken = useSetAtom(token) + const setErr = useSetAtom(LayoutAlertErr) + const setOpen = useSetAtom(DrawerOpen) + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + + const handleLogOut = useMemoizedFn(() => { + setAnchorEl(null); + setNowUser({ name: "", uuid: "" }) + setToken("") + navigate("/login") + }) + + + + return ( + <> + + {nowUser.name != "" && (<> + setOpen(true)} + > + + + ) + } + + + {server.data?.serverName ?? "皮肤站"} + + + {nowUser.name != "" && ( +
+ setAnchorEl(event.currentTarget)} + color="inherit" + > + + + setAnchorEl(null)} + > + 登出 + +
+ )} + {nowUser.name == "" && ( + + )} +
+ ) +}) + +const MyList = memo(function MyList(p: { list: ListItem[] }) { + + + return ( + <> + + {p.list.map(item => + + )} + + + ) +}) + +const MyListItem = function MyListItem(p: ListItem) { + const navigate = useNavigate(); + + const handleClick = () => { + navigate(p.link) + } + + return ( + + + + {p.icon} + + + + + ) +} + +const MyDrawer = function MyDrawer() { + const nowToken = useAtomValue(token) + const setErr = useSetAtom(LayoutAlertErr) + const theme = useTheme(); + const isLg = useMediaQuery(theme.breakpoints.up('lg')) + const [open, setOpen] = useAtom(DrawerOpen) + const navigate = useNavigate(); + + const userinfo = useRequest(() => userInfo(nowToken), { + refreshDeps: [nowToken], + cacheKey: "/api/v1/user" + nowToken, + staleTime: 60000, + onError: e => { + if (e instanceof ApiErr && e.code == 5) { + navigate("/login") + } + console.warn(e) + setErr(String(e)) + }, + }) + + const userDrawerList = React.useMemo(() => [ + { + icon: , + title: '个人信息', + link: '/profile' + }, + { + icon: , + title: '皮肤设置', + link: '/textures' + }, + { + icon: , + title: '账号设置', + link: '/security' + } + ] as ListItem[], []) + + const adminDrawerList = React.useMemo(() => [ + { + icon: , + title: '用户管理', + link: '/admin/user' + } + ] as ListItem[], []) + + + + return (<> + {userinfo.data && ( + setOpen(false)} + > + + setOpen(false)}> + {theme.direction === 'ltr' ? : } + + + + + {userinfo.data?.is_admin && ( + <> + + + )} + + )} + ) +} + +export default Layout \ No newline at end of file diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx new file mode 100644 index 0000000..8d1127d --- /dev/null +++ b/frontend/src/views/Login.tsx @@ -0,0 +1,169 @@ +import * as React from 'react'; +import { useState } from 'react'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Link from '@mui/material/Link'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import { useSetAtom } from 'jotai'; +import { token, user } from '@/store/store' +import { getConfig, login } from '@/apis/apis' +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import Loading from '@/components/Loading' +import CheckInput, { refType } from '@/components/CheckInput' +import useTitle from '@/hooks/useTitle'; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import { ApiErr } from '@/apis/error'; +import { useRequest } from 'ahooks'; + + + +export default function SignIn() { + const [err, setErr] = useState(""); + const [loading, setLoading] = useState(false); + const setToken = useSetAtom(token) + const setUserInfo = useSetAtom(user) + const checkList = React.useRef>(new Map()) + const navigate = useNavigate(); + useTitle("登录") + const captchaRef = React.useRef(null) + const [captchaToken, setCaptchaToken] = useState(""); + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + const data = new FormData(event.currentTarget); + const postData = { + email: data.get('email')?.toString(), + password: data.get('password')?.toString(), + } + if (!Array.from(checkList.current.values()).map(v => v.verify()).reduce((p, v) => (p == true) && (v == true))) { + return + } + + if (loading) return + setLoading(true) + login(postData.email!, postData.password ?? "", captchaToken). + then(v => { + if (!v) return + setToken(v.token) + setUserInfo({ + uuid: v.uuid, + name: v.name, + }) + navigate("/profile") + }). + catch(v => { + captchaRef.current?.reload() + console.warn(v) + if (v instanceof ApiErr) { + switch (v.code) { + case 10: + setErr("验证码错误") + return + case 6: + setErr("密码或用户名错误") + return + case 9: + setErr("用户已被禁用") + return + } + } + setErr(String(v)) + }). + finally(() => setLoading(false)) + + }; + + + return ( + + + + + + + 登录 + + + { + dom && checkList.current.set("1", dom) + }} + checkList={[ + { + errMsg: "需为邮箱", + reg: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + } + ]} + margin="normal" + fullWidth + id="email" + label="邮箱" + name="email" + autoComplete="email" + autoFocus + /> + + + + + + {server.data?.NeedEmail && + 忘记密码? + } + + + + {"注册"} + + + + + + + setErr("")} severity="error">{err} + + {loading && } + + ); +} \ No newline at end of file diff --git a/frontend/src/views/Register.tsx b/frontend/src/views/Register.tsx new file mode 100644 index 0000000..192f9dd --- /dev/null +++ b/frontend/src/views/Register.tsx @@ -0,0 +1,225 @@ +import * as React from 'react'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Link from '@mui/material/Link'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import { Link as RouterLink } from "react-router-dom"; +import { getConfig, register } from '@/apis/apis' +import CheckInput, { refType } from '@/components/CheckInput' +import { useRef, useState } from 'react'; +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import Loading from '@/components/Loading' +import { useNavigate } from "react-router-dom"; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import useTitle from '@/hooks/useTitle'; +import { ApiErr } from '@/apis/error'; +import { useSetAtom } from 'jotai'; +import { token, user } from '@/store/store'; +import { useRequest } from 'ahooks'; + +export default function SignUp() { + const [regErr, setRegErr] = useState(""); + const navigate = useNavigate(); + const [captchaToken, setCaptchaToken] = useState(""); + const captchaRef = useRef(null) + const [loading, setLoading] = useState(false); + useTitle("注册") + const setToken = useSetAtom(token) + const setUserInfo = useSetAtom(user) + const [code, setCode] = useState("") + const [email, setEmail] = useState("") + const [disableEmail, setDisableEmail] = useState(false) + + const u = new URL(location.href) + + React.useEffect(() => { + const e = u.searchParams.get("email") + if (!e || e == "") return + setEmail(e) + setDisableEmail(true) + }, [u.searchParams]) + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setRegErr(String(e)) + } + }) + + React.useEffect(() => { + if (!server.data || !server.data.NeedEmail) return + + const code = u.searchParams.get("code") + if (!code || code == "") { + navigate("/register_email") + return + } + setCode(code) + }, [server.data, u.searchParams, navigate]) + + + + const checkList = React.useRef>(new Map()) + + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (loading) return + const data = new FormData(event.currentTarget); + const d = { + password: data.get('password')?.toString(), + username: data.get("username")?.toString() + } + if (!Array.from(checkList.current.values()).map(v => v.verify()).reduce((p, v) => (p == true) && (v == true))) { + return + } + if (captchaToken == "") { + setRegErr("验证码无效") + return + } + setLoading(true) + register(email ?? "", d.username ?? "", d.password ?? "", captchaToken, code). + then(v => { + if (!v) return + setToken(v.token) + setUserInfo({ + uuid: v.uuid, + name: v.name, + }) + navigate("/profile") + }). + catch(v => { + captchaRef.current?.reload() + console.warn(v) + if (v instanceof ApiErr) { + switch (v.code) { + case 10: + setRegErr("验证码错误") + return + case 3: + setRegErr("邮箱已存在") + return + case 7: + setRegErr("用户名已存在") + return + } + } + setRegErr(String(v)) + }). + finally(() => setLoading(false)) + }; + + return ( + + + + + + + + 注册 + + + + + setEmail(v.target.value)} + autoComplete="email" + ref={(dom) => { + dom && checkList.current.set("1", dom) + }} + /> + + + { + dom && checkList.current.set("2", dom) + }} + checkList={[ + { + errMsg: "长度在 3-16 之间", + reg: /^.{3,16}$/ + } + ]} + required + fullWidth + name="username" + label="角色名" + autoComplete="username" + /> + + + { + dom && checkList.current.set("3", dom) + }} + checkList={[ + { + errMsg: "长度在 6-50 之间", + reg: /^.{6,50}$/ + } + ]} + required + fullWidth + label="密码" + type="password" + name="password" + autoComplete="new-password" + /> + + + + + + + + + + 登录 + + + + + + + setRegErr("")} severity="error">{regErr} + + {loading && } + + ); +} \ No newline at end of file diff --git a/frontend/src/views/SendEmail.tsx b/frontend/src/views/SendEmail.tsx new file mode 100644 index 0000000..6fb1126 --- /dev/null +++ b/frontend/src/views/SendEmail.tsx @@ -0,0 +1,181 @@ +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import TextField from '@mui/material/TextField'; +import { useRequest, useTitle } from 'ahooks'; +import { getConfig } from '@/apis/apis'; +import { useEffect, useRef, useState } from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import { useNavigate } from "react-router-dom"; +import { ApiErr } from '@/apis/error'; +import Loading from '@/components/Loading'; + +export default function SendEmail({ title, anyEmail = false, sendService }: { title: string, anyEmail?: boolean, sendService: (email: string, captchaToken: string) => Promise }) { + const [err, setErr] = useState(""); + const [domain, setDomain] = useState(""); + const [email, setEmail] = useState("") + const captchaRef = useRef(null) + const [captchaToken, setCaptchaToken] = useState(""); + const [open, setOpen] = useState(false); + useTitle(title) + const navigate = useNavigate(); + const [helperText, setHelperText] = useState("") + const [loading, setLoading] = useState(false); + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + useEffect(() => { + if (server.data?.AllowDomain.length != 0) { + setDomain(server.data?.AllowDomain[0] ?? "") + } + }, [server.data?.AllowDomain]) + + const emailonChange = (e: React.ChangeEvent) => { + setEmail(e.target.value) + if (e.target.value == "") { + setHelperText("邮箱不得为空") + } + setHelperText("") + } + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (email == "") { + setHelperText("邮箱不得为空") + } + const sendEmail = (() => { + if (!anyEmail && domain != "") { + return `${email}@${domain}` + } + return email + })() + + if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)) { + setHelperText("邮箱格式错误") + return + } + if (!anyEmail && server.data?.EmailReg && server.data?.EmailReg != "" + && !new RegExp(server.data?.EmailReg).test(sendEmail)) { + setHelperText(server.data?.EmailRegMsg ?? "邮箱不满足正则要求") + return + } + + if (server.data?.captcha.type != "" && captchaToken == "") { + return + } + setLoading(true) + sendService(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => { + captchaRef.current?.reload() + console.warn(e) + if (e instanceof ApiErr) { + switch (e.code) { + case 10: + setErr("验证码错误") + return + case 11: + setErr("暂时无法对此邮箱发送邮件") + return + } + } + setErr(String(e)) + }).finally(() => setLoading(false)) + + } + + const handleClose = () => { + navigate("/") + } + + + return ( + + + + + + + + {title} + + + + + + { + server.data?.AllowDomain.length != 0 && !anyEmail && + + 域名 + + + } + + + + + + + + + + setErr("")} severity="error">{err} + + + 邮件已发送 + + 请到收件箱(或垃圾箱)点击验证链接以继续。 + + + + + + {loading && } + + ) +} \ No newline at end of file diff --git a/frontend/src/views/admin/UserAdmin.tsx b/frontend/src/views/admin/UserAdmin.tsx new file mode 100644 index 0000000..8e4d320 --- /dev/null +++ b/frontend/src/views/admin/UserAdmin.tsx @@ -0,0 +1,237 @@ +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import useTitle from '@/hooks/useTitle'; +import { useRequest } from 'ahooks'; +import { ListUser, editUser } from '@/apis/apis'; +import { useEffect, useRef, useState } from 'react'; +import { useAtomValue } from 'jotai'; +import { token } from '@/store/store'; +import TablePagination from '@mui/material/TablePagination'; +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Box from '@mui/material/Box'; +import Chip from '@mui/material/Chip'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import { EditUser, UserInfo } from '@/apis/model'; +import { produce } from 'immer' +import Checkbox from '@mui/material/Checkbox'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import SkinViewUUID from '@/components/SkinViewUUID'; +import Loading from '@/components/Loading'; + +export default function UserAdmin() { + useTitle("用户管理") + const [page, setPage] = useState(1) + const nowtoken = useAtomValue(token) + const [err, setErr] = useState("") + const [email, setEmail] = useState("") + const [name, setName] = useState("") + const [open, setOpen] = useState(false); + const [row, setRow] = useState(null) + + + const handleOpen = (row: UserInfo) => { + setRow(row) + setOpen(true) + } + + const uq = new URLSearchParams("/api/v1/admin/users") + uq.set("page", String(page)) + uq.set("email", email) + uq.set("name", name) + + const { data, run } = useRequest(ListUser, { + cacheKey: uq.toString(), + debounceWait: 300, + onError: e => { + setErr(String(e)) + } + }) + useEffect(() => { + run(page, nowtoken, email, name) + }, [page, nowtoken, run, email, name]) + + + return (<> + + + + setEmail(v.target.value)} label="邮箱" variant="standard" /> + setName(v.target.value)} label="用户名" variant="standard" /> + + + + + + 邮箱 + 用户名 + 注册 IP + UUID + + + + + {data?.list.map((row) => ( + + {row.email} + {row.name} + {row.reg_ip} + {row.uuid} + + + ))} + +
+
+ setPage(page + 1)} + /> +
+ + setErr("")} severity="error">{err} + + + run(page, nowtoken, email, name)} /> + ); +} + +interface MyDialogProp { + open: boolean + setOpen: (b: boolean) => void + row: UserInfo | null + onUpdate: () => void +} + +function MyDialog({ open, row, setOpen, onUpdate }: MyDialogProp) { + const handleClose = () => { + setOpen(false) + } + const [erow, setErow] = useState({ + email: "", + name: "", + password: "", + is_admin: false, + is_disable: false, + del_textures: false, + }) + const [load, setLoad] = useState(false) + const nowToken = useAtomValue(token) + const [err, setErr] = useState("") + const editValue = useRef({}); + + useEffect(() => { + if (!row) return + setErow({ + email: row.email, + name: row.name, + password: "", + is_admin: row.is_admin, + is_disable: row.is_disable, + del_textures: false, + }) + editValue.current = {} + }, [row, open]) + + const handleOpen = () => { + if (load) return + setLoad(true) + editUser(editValue.current, nowToken, String(row?.uid)).then(() => [setOpen(false), onUpdate(), editValue.current = {}]).finally(() => setLoad(false)). + catch(e => setErr(String(e))) + } + + type StringKeys = { + [K in keyof T]: T[K] extends string ? K : never; + }[keyof T]; + + function handleSetValue(key: StringKeys>, value: string) { + setErow(produce(v => { + v[key] = value + editValue.current[key] = value + })) + } + + type BoolKeys = { + [K in keyof T]: T[K] extends boolean ? K : never; + }[keyof T]; + + function handleSetChecked(key: BoolKeys>, value: boolean) { + setErow(produce(v => { + v[key] = value + editValue.current[key] = value + })) + } + + + return (<> + + 修改用户信息 + + + handleSetValue('email', e.target.value)} + /> + handleSetValue('name', e.target.value)} + /> + handleSetValue('password', e.target.value)} + /> + + + handleSetChecked('is_admin', e.target.checked)} />} label="管理权限" /> + handleSetChecked('is_disable', e.target.checked)} />} label="禁用" /> + handleSetChecked('del_textures', e.target.checked)} />} label="清空材质" /> + + + + + + + + + + + {load && } + + setErr("")} severity="error">{err} + + ) +} \ No newline at end of file diff --git a/frontend/src/views/profile/Profile.tsx b/frontend/src/views/profile/Profile.tsx new file mode 100644 index 0000000..98cbc2c --- /dev/null +++ b/frontend/src/views/profile/Profile.tsx @@ -0,0 +1,70 @@ +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import CardHeader from '@mui/material/CardHeader'; +import { user } from '@/store/store'; +import { useAtomValue } from 'jotai'; +import { useNavigate } from 'react-router-dom'; +import Box from '@mui/material/Box'; +import useTitle from '@/hooks/useTitle'; +import SkinViewUUID from '@/components/SkinViewUUID'; +import root from '@/utils/root'; + +const Profile = function Profile() { + const navigate = useNavigate(); + const userinfo = useAtomValue(user) + + useTitle("个人信息") + + + return ( + <> + + + + + 用户名 + {userinfo.name} + UUID + {userinfo.uuid} + + {/* + + */} + + + + + + + + + + + + + + 本站 Yggdrasil 认证服务器地址 + {getYggRoot()} + 点击下方按钮复制 API 地址,或者将按钮拖动至启动器的任意界面即可快速添加认证服务器。 + + + + + + ) +} + +function getYggRoot() { + const u = new URL(root() + "/api/yggdrasil") + return u.toString() +} + +export default Profile \ No newline at end of file diff --git a/frontend/src/views/profile/Security.tsx b/frontend/src/views/profile/Security.tsx new file mode 100644 index 0000000..94f405c --- /dev/null +++ b/frontend/src/views/profile/Security.tsx @@ -0,0 +1,202 @@ +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardHeader from "@mui/material/CardHeader"; +import TextField from "@mui/material/TextField"; +import { useEffect, useState } from "react"; +import { produce } from 'immer' +import { changeName, changePasswd, getConfig } from "@/apis/apis"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { LayoutAlertErr, token, user } from "@/store/store"; +import Loading from "@/components/Loading"; +import { ApiErr } from "@/apis/error"; +import { useNavigate } from "react-router-dom"; +import useTitle from "@/hooks/useTitle"; +import Box from "@mui/material/Box"; +import { useRequest } from "ahooks"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; + +export default function Security() { + useTitle("账号设置") + const setLayoutErr = useSetAtom(LayoutAlertErr) + + const { data } = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 600000, + onError: e => { + setLayoutErr(String(e)) + } + }) + + return (<> + + + {data?.AllowChangeName && } + + ) +} + +function ChangePasswd() { + const [pass, setPass] = useState({ + old: "", + pass1: "", + pass2: "", + }) + const [err, setErr] = useState("") + const [oldPassErr, setOldPassErr] = useState(false) + const [nowToken, setToken] = useAtom(token) + const [load, setLoad] = useState(false) + const setLayoutErr = useSetAtom(LayoutAlertErr) + const setUser = useSetAtom(user) + const navigate = useNavigate(); + + useEffect(() => { + if (pass.pass1 != pass.pass2 && pass.pass2 != "") { + setErr("密码不相等") + return + } + setErr("") + }, [pass.pass1, pass.pass2]) + + const handelClick = () => { + if (pass.pass1 != pass.pass2) return + if (load) return + setLoad(true) + changePasswd(pass.old, pass.pass1, nowToken) + .then(() => [navigate("/login"), setToken(""), setUser({ name: "", uuid: "" })]) + .catch(e => { + if (e instanceof ApiErr && e.code == 6) { + setOldPassErr(true) + return + } + setLayoutErr(String(e)) + }).finally(() => setLoad(false)) + } + + + return (<> + + + + setPass(produce(v => { v.old = p.target.value }))} + autoComplete="current-password" + /> + setPass(produce(v => { v.pass1 = p.target.value }))} + autoComplete="new-password" + /> + setPass(produce(v => { v.pass2 = p.target.value }))} + autoComplete="new-password" + /> + + + + {load && } + + ) +} + +function ChangeName() { + const [err, setErr] = useState("") + const [name, setName] = useState("") + const [open, setOpen] = useState(false) + const [load, setLoad] = useState(false) + const nowToken = useAtomValue(token) + const setUser = useSetAtom(user) + + const handelClick = () => { + if (name == "") return + setOpen(true) + } + + const handleClose = () => { + setOpen(false) + } + + const handleSubmit = () => { + if (load) return + setLoad(true) + changeName(name, nowToken).then(() => { + setName("") + setUser(v => { return { name: name, uuid: v.uuid } }) + }).catch(e => { + if (e instanceof ApiErr && e.code == 7) { + setErr("用户名已存在") + return + } + setErr(String(e)) + console.warn(e) + }).finally(() => [setLoad(false), setOpen(false)]) + } + + return (<> + + + + setName(v.target.value)} + autoComplete="username" + /> + + + + + + 确认修改后的用户名 + + + + {`用户名改为`} { {name} } {`?`} + + + + + + + + {load && } + ) +} \ No newline at end of file diff --git a/frontend/src/views/profile/Textures.tsx b/frontend/src/views/profile/Textures.tsx new file mode 100644 index 0000000..8240834 --- /dev/null +++ b/frontend/src/views/profile/Textures.tsx @@ -0,0 +1,133 @@ +import { useEffect, useState } from "react"; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Radio from "@mui/material/Radio"; +import RadioGroup from "@mui/material/RadioGroup"; +import Button from "@mui/material/Button"; +import { CardHeader } from "@mui/material"; +import useTitle from "@/hooks/useTitle"; +import { MuiFileInput } from 'mui-file-input' +import Box from "@mui/material/Box"; +import ReactSkinview3d from '@/components/Skinview3d' +import { useUnmount } from "ahooks"; +import { useAtomValue, useSetAtom } from "jotai"; +import { LayoutAlertErr, token } from "@/store/store"; +import { upTextures } from "@/apis/apis"; +import Loading from "@/components/Loading"; +import Snackbar from "@mui/material/Snackbar"; + +const Textures = function Textures() { + const [redioValue, setRedioValue] = useState("skin") + useTitle("上传皮肤") + const [file, setFile] = useState(null) + const setErr = useSetAtom(LayoutAlertErr) + const [loading, setLoading] = useState(false) + const nowToken = useAtomValue(token) + const [ok, setOk] = useState(false) + const [skinInfo, setSkinInfo] = useState({ + skin: "", + cape: "", + model: "default" + }) + + useUnmount(() => { + skinInfo.skin && URL.revokeObjectURL(skinInfo.skin) + skinInfo.cape && URL.revokeObjectURL(skinInfo.cape) + }) + + useEffect(() => { + if (file) { + setSkinInfo(v => { + URL.revokeObjectURL(v.skin); + URL.revokeObjectURL(v.cape); + return { skin: "", cape: "", model: "" } + }) + const nu = URL.createObjectURL(file) + switch (redioValue) { + case "skin": + setSkinInfo({ skin: nu, cape: "", model: "default" }) + break + case "slim": + setSkinInfo({ skin: nu, cape: "", model: "slim" }) + break + case "cape": + setSkinInfo({ skin: "", cape: nu, model: "slim" }) + } + } + }, [file, redioValue]) + + + const onRadioChange = (_a: React.ChangeEvent, value: string) => { + setRedioValue(value) + } + const handleChange = (newFile: File | null) => { + setFile(newFile) + } + + const handleToUpload = () => { + if (!file || loading) return + setLoading(true) + const textureType = redioValue == "cape" ? "cape" : "skin" + const model = redioValue == "slim" ? "slim" : "" + upTextures(nowToken, textureType, model, file).then(() => setOk(true)).catch(e => [setErr(String(e)), console.warn(e)]). + finally(() => setLoading(false)) + } + + + + return (<> + + + + + + 类型 + + } label="Steve" /> + } label="Alex" /> + } label="披风" /> + +
+ +
+ +
+
+
+ + + + {file && } + + +
+ setOk(false)} + message="成功" + /> + {loading && } + ) +} + +export default Textures \ No newline at end of file diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..9fd60fd --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + }, + }, + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} \ No newline at end of file diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..99019d2 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { join } from "path"; +import legacy from '@vitejs/plugin-legacy' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + react(), + legacy() + ], + resolve: { + alias: { + '@': join(__dirname, "src") + }, + } +}) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3c8e4c5 --- /dev/null +++ b/go.mod @@ -0,0 +1,49 @@ +module github.com/xmdhs/authlib-skin + +go 1.23 + +require ( + entgo.io/ent v0.12.3 + github.com/VictoriaMetrics/fastcache v1.12.1 + github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56 + github.com/go-chi/chi/v5 v5.0.10 + github.com/go-chi/cors v1.2.1 + github.com/go-playground/validator/v10 v10.15.3 + github.com/go-sql-driver/mysql v1.7.1 + github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/google/uuid v1.3.1 + github.com/google/wire v0.5.0 + github.com/mattn/go-sqlite3 v1.14.17 + github.com/pelletier/go-toml/v2 v2.1.0 + github.com/redis/go-redis/v9 v9.2.1 + github.com/samber/lo v1.38.1 + github.com/stretchr/testify v1.8.4 + github.com/wneessen/go-mail v0.4.0 + golang.org/x/crypto v0.14.0 +) + +require ( + ariga.io/atlas v0.10.2-0.20230427182402-87a07dfb83bf // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-openapi/inflect v0.19.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/hashicorp/hcl/v2 v2.13.0 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/zclconf/go-cty v1.8.0 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d721c23 --- /dev/null +++ b/go.sum @@ -0,0 +1,136 @@ +ariga.io/atlas v0.10.2-0.20230427182402-87a07dfb83bf h1:Tq2DRB39ZHScIwWACjPKLv5oEErv7zv6PBb5RTz5CKA= +ariga.io/atlas v0.10.2-0.20230427182402-87a07dfb83bf/go.mod h1:+TR129FJZ5Lvzms6dvCeGWh1yR6hMvmXBhug4hrNIGk= +entgo.io/ent v0.12.3 h1:N5lO2EOrHpCH5HYfiMOCHYbo+oh5M8GjT0/cx5x6xkk= +entgo.io/ent v0.12.3/go.mod h1:AigGGx+tbrBBYHAzGOg8ND661E5cxx1Uiu5o/otJ6Yg= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56 h1:CXWdlGkIdY4W1KGym1dFxwzRrLhneeonNSOwrhuhwQM= +github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56/go.mod h1:v4e05/vzE8ubOim1No9Xx5eIQ/WRq6AtcnQIy/Z/JPs= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= +github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= +github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/wneessen/go-mail v0.4.0 h1:Oo4HLIV8My7G9JuZkoOX6eipXQD+ACvIqURYeIzUc88= +github.com/wneessen/go-mail v0.4.0/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= +github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handle/admin.go b/handle/admin.go new file mode 100644 index 0000000..2a1a31b --- /dev/null +++ b/handle/admin.go @@ -0,0 +1,137 @@ +package handle + +import ( + "context" + "log/slog" + "net/http" + "strconv" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/go-playground/validator/v10" + "github.com/xmdhs/authlib-skin/handle/handelerror" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/service" + + U "github.com/xmdhs/authlib-skin/utils" +) + +type AdminHandel struct { + handleError *handelerror.HandleError + adminService *service.AdminService + validate *validator.Validate +} + +func NewAdminHandel(handleError *handelerror.HandleError, adminService *service.AdminService, validate *validator.Validate) *AdminHandel { + return &AdminHandel{ + handleError: handleError, + adminService: adminService, + validate: validate, + } +} + +type tokenValue string + +const tokenKey = tokenValue("token") + +func (h *AdminHandel) NeedAuth(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + token := h.getTokenbyAuthorization(ctx, w, r) + if token == "" { + return + } + t, err := h.adminService.Auth(ctx, token) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + r = r.WithContext(context.WithValue(ctx, tokenKey, t)) + handle.ServeHTTP(w, r) + }) +} + +func (h *AdminHandel) NeedAdmin(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + t := ctx.Value(tokenKey).(*model.TokenClaims) + err := h.adminService.IsAdmin(ctx, t) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + handle.ServeHTTP(w, r) + }) +} + +func (h *AdminHandel) ListUser() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + page := r.FormValue("page") + pagei := 1 + if page != "" { + p, err := strconv.Atoi(page) + if err != nil { + h.handleError.Error(ctx, w, "page 必须为数字", model.ErrInput, 400, slog.LevelDebug) + return + } + if p == 0 { + p = 1 + } + pagei = p + } + email := r.FormValue("email") + name := r.FormValue("name") + + ul, uc, err := h.adminService.ListUser(ctx, pagei, email, name) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[model.List[model.UserList]]{Data: model.List[model.UserList]{List: ul, Total: uc}}) + } +} + +func (h *AdminHandel) EditUser() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + uid := chi.URLParamFromCtx(ctx, "uid") + if uid == "" { + h.handleError.Error(ctx, w, "uid 为空", model.ErrInput, 400, slog.LevelDebug) + return + } + uidi, err := strconv.Atoi(uid) + if err != nil { + h.handleError.Error(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.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + err = h.adminService.EditUser(ctx, a, uidi) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson[any](w, model.API[any]{ + Code: 0, + }) + } +} + +func (h *AdminHandel) getTokenbyAuthorization(ctx context.Context, w http.ResponseWriter, r *http.Request) string { + auth := r.Header.Get("Authorization") + if auth == "" { + h.handleError.Error(ctx, w, "缺少 Authorization", model.ErrAuth, 401, slog.LevelDebug) + return "" + } + al := strings.Split(auth, " ") + if len(al) != 2 || al[0] != "Bearer" { + h.handleError.Error(ctx, w, "Authorization 格式错误", model.ErrAuth, 401, slog.LevelDebug) + return "" + } + return al[1] +} diff --git a/handle/config.go b/handle/config.go new file mode 100644 index 0000000..b87a32b --- /dev/null +++ b/handle/config.go @@ -0,0 +1,31 @@ +package handle + +import ( + "encoding/json" + "net/http" + + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/service" +) + +type Handel struct { + webService *service.WebService +} + +func NewHandel(webService *service.WebService) *Handel { + return &Handel{ + webService: webService, + } +} + +func (h *Handel) GetConfig() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + c := h.webService.GetConfig(ctx) + m := model.API[model.Config]{ + Code: 0, + Data: c, + } + json.NewEncoder(w).Encode(m) + } +} diff --git a/handle/handelerror/error.go b/handle/handelerror/error.go new file mode 100644 index 0000000..ac68ea4 --- /dev/null +++ b/handle/handelerror/error.go @@ -0,0 +1,67 @@ +package handelerror + +import ( + "context" + "encoding/json" + "errors" + "log/slog" + "net/http" + + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" + "github.com/xmdhs/authlib-skin/service/email" +) + +type HandleError struct { + logger *slog.Logger +} + +func NewHandleError(logger *slog.Logger) *HandleError { + return &HandleError{ + logger: logger, + } +} + +type errorHandler struct { + ErrorType error + ModelError model.APIStatus + StatusCode int + LogLevel slog.Level +} + +var errorHandlers = []errorHandler{ + {service.ErrExistUser, model.ErrExistUser, 400, slog.LevelDebug}, + {service.ErrExitsName, model.ErrExitsName, 400, slog.LevelDebug}, + {service.ErrRegLimit, model.ErrRegLimit, 400, slog.LevelInfo}, + {captcha.ErrCaptcha, model.ErrCaptcha, 400, slog.LevelDebug}, + {service.ErrPassWord, model.ErrPassWord, 401, slog.LevelInfo}, + {auth.ErrUserDisable, model.ErrUserDisable, 401, slog.LevelDebug}, + {service.ErrNotAdmin, model.ErrNotAdmin, 401, slog.LevelDebug}, + {auth.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug}, + {email.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug}, + {email.ErrSendLimit, model.ErrEmailSend, 403, slog.LevelDebug}, + {service.ErrUsername, model.ErrPassWord, 401, slog.LevelInfo}, +} + +func (h *HandleError) Service(ctx context.Context, w http.ResponseWriter, err error) { + for _, errorHandler := range errorHandlers { + if errors.Is(err, errorHandler.ErrorType) { + h.Error(ctx, w, err.Error(), errorHandler.ModelError, errorHandler.StatusCode, errorHandler.LogLevel) + return + } + } + + h.Error(ctx, w, err.Error(), model.ErrService, 500, slog.LevelWarn) +} + +func (h *HandleError) Error(ctx context.Context, w http.ResponseWriter, msg string, code model.APIStatus, httpcode int, level slog.Level) { + h.logger.Log(ctx, level, msg) + w.WriteHeader(httpcode) + b, err := json.Marshal(model.API[any]{Code: code, Msg: msg, Data: nil}) + if err != nil { + panic(err) + } + w.Write(b) +} diff --git a/handle/handle.go b/handle/handle.go new file mode 100644 index 0000000..339bd88 --- /dev/null +++ b/handle/handle.go @@ -0,0 +1,29 @@ +package handle + +import ( + "encoding/json" + "fmt" + "io" + "net/netip" + + "github.com/google/wire" + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/model" +) + +var HandelSet = wire.NewSet(NewUserHandel, NewAdminHandel, NewHandel) + +func encodeJson[T any](w io.Writer, m model.API[T]) { + json.NewEncoder(w).Encode(m) +} + +func getPrefix(ip string) (string, error) { + ipa, err := netip.ParseAddr(ip) + if err != nil { + return "", fmt.Errorf("getPrefix: %w", err) + } + if ipa.Is6() { + return lo.Must1(ipa.Prefix(48)).String(), nil + } + return lo.Must1(ipa.Prefix(24)).String(), nil +} diff --git a/handle/user.go b/handle/user.go new file mode 100644 index 0000000..b0af80e --- /dev/null +++ b/handle/user.go @@ -0,0 +1,338 @@ +package handle + +import ( + "bytes" + "context" + "errors" + "fmt" + "image/png" + "io" + "log/slog" + "net/http" + "regexp" + "strings" + "sync" + + "github.com/go-chi/chi/v5" + "github.com/go-playground/validator/v10" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/handle/handelerror" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/utils" +) + +type UserHandel struct { + handleError *handelerror.HandleError + validate *validator.Validate + userService *service.UserService + logger *slog.Logger + textureService *service.TextureService + config config.Config + + emailReg func() (*regexp.Regexp, error) +} + +func NewUserHandel(handleError *handelerror.HandleError, validate *validator.Validate, + userService *service.UserService, logger *slog.Logger, textureService *service.TextureService, config config.Config) *UserHandel { + emailReg := sync.OnceValues[*regexp.Regexp, error](func() (*regexp.Regexp, error) { + return regexp.Compile(config.Email.EmailReg) + }) + + return &UserHandel{ + handleError: handleError, + validate: validate, + userService: userService, + logger: logger, + textureService: textureService, + config: config, + + emailReg: emailReg, + } +} + +func (h *UserHandel) Reg() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ip, err := utils.GetIP(r) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + + u, err := utils.DeCodeBody[model.UserReg](r.Body, h.validate) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + rip, err := getPrefix(ip) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrUnknown, 500, slog.LevelWarn) + return + } + lr, err := h.userService.Reg(ctx, u, rip, ip) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[model.LoginRep]{ + Code: 0, + Data: lr, + }) + } +} + +func (h *UserHandel) Login() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ip, err := utils.GetIP(r) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + + l, err := utils.DeCodeBody[model.Login](r.Body, h.validate) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + + lr, err := h.userService.Login(ctx, l, ip) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[model.LoginRep]{ + Code: 0, + Data: lr, + }) + } +} + +func (h *UserHandel) UserInfo() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + t := ctx.Value(tokenKey).(*model.TokenClaims) + u, err := h.userService.Info(ctx, t) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[model.UserInfo]{ + Code: 0, + Data: u, + }) + } +} + +func (h *UserHandel) ChangePasswd() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + t := ctx.Value(tokenKey).(*model.TokenClaims) + + c, err := utils.DeCodeBody[model.ChangePasswd](r.Body, h.validate) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + err = h.userService.ChangePasswd(ctx, c, t.UID, true) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + + } +} + +func (h *UserHandel) ChangeName() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + t := ctx.Value(tokenKey).(*model.TokenClaims) + c, err := utils.DeCodeBody[model.ChangeName](r.Body, h.validate) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + err = h.userService.ChangeName(ctx, c.Name, t) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + } +} + +func (h *UserHandel) 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.Error(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.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + + switch models { + case "slim": + case "": + default: + h.logger.DebugContext(ctx, "错误的皮肤的材质模型") + h.handleError.Error(ctx, w, "错误的皮肤的材质模型", model.ErrInput, 400, slog.LevelDebug) + return + } + + err = h.textureService.PutTexture(ctx, t, skin, models, textureType) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + } +} + +func (h *UserHandel) NeedEnableEmail(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if !h.config.Email.Enable { + h.handleError.Error(ctx, w, "未开启邮件功能", model.ErrUnknown, 403, slog.LevelInfo) + } + handle.ServeHTTP(w, r) + }) +} + +var ErrNotAllowDomain = errors.New("不在允许域名列表内") + +func (h *UserHandel) SendRegEmail() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + c, ip, shouldReturn := h.sendMailParameter(ctx, r, w) + if shouldReturn { + return + } + + if len(h.config.Email.AllowDomain) != 0 { + allow := false + for _, v := range h.config.Email.AllowDomain { + if strings.HasSuffix(c.Email, v) { + allow = true + break + } + } + if !allow { + h.handleError.Error(ctx, w, "不在允许邮箱域名内", model.ErrInput, 400, slog.LevelDebug) + return + } + } + if h.config.Email.EmailReg != "" { + r, err := h.emailReg() + if err != nil { + h.handleError.Error(ctx, w, "正则错误", model.ErrUnknown, 500, slog.LevelError) + return + } + if !r.MatchString(c.Email) { + h.handleError.Error(ctx, w, "邮箱不符合正则要求", model.ErrInput, 400, slog.LevelDebug) + return + } + } + + err := h.userService.SendRegEmail(ctx, c.Email, c.CaptchaToken, r.Host, ip) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + } +} + +func (h *UserHandel) SendForgotEmail() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + c, ip, shouldReturn := h.sendMailParameter(ctx, r, w) + if shouldReturn { + return + } + + err := h.userService.SendChangePasswordEmail(ctx, c.Email, c.CaptchaToken, r.Host, ip) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + } +} + +func (h *UserHandel) sendMailParameter(ctx context.Context, r *http.Request, w http.ResponseWriter) (model.SendRegEmail, string, bool) { + c, err := utils.DeCodeBody[model.SendRegEmail](r.Body, h.validate) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return model.SendRegEmail{}, "", true + } + ip, err := utils.GetIP(r) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return model.SendRegEmail{}, "", true + } + return c, ip, false +} + +func (h *UserHandel) ForgotPassword() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + c, err := utils.DeCodeBody[model.ForgotPassword](r.Body, h.validate) + if err != nil { + h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) + return + } + err = h.userService.ForgotPassword(ctx, c.Email, c.PassWord, c.EmailJwt) + if err != nil { + h.handleError.Service(ctx, w, err) + return + } + encodeJson(w, model.API[any]{ + Code: 0, + }) + } +} diff --git a/handle/yggdrasil/error.go b/handle/yggdrasil/error.go new file mode 100644 index 0000000..920aab8 --- /dev/null +++ b/handle/yggdrasil/error.go @@ -0,0 +1,23 @@ +package yggdrasil + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/xmdhs/authlib-skin/model/yggdrasil" +) + +func handleYgError(ctx context.Context, w http.ResponseWriter, e yggdrasil.Error, httpcode int) { + w.WriteHeader(httpcode) + b, err := json.Marshal(e) + if err != nil { + panic(err) + } + w.Write(b) +} + +func (y *Yggdrasil) handleYgError(ctx context.Context, w http.ResponseWriter, err error) { + y.logger.WarnContext(ctx, err.Error()) + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: err.Error()}, 500) +} diff --git a/handle/yggdrasil/session.go b/handle/yggdrasil/session.go new file mode 100644 index 0000000..1501946 --- /dev/null +++ b/handle/yggdrasil/session.go @@ -0,0 +1,54 @@ +package yggdrasil + +import ( + "encoding/json" + "net/http" + + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/utils" +) + +func (y *Yggdrasil) SessionJoin() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + a, has := getAnyModel[yggdrasil.Session](ctx, w, r.Body, y.validate, y.logger) + if !has { + return + } + t := ctx.Value(tokenKey).(*model.TokenClaims) + + ip, err := utils.GetIP(r) + if err != nil { + y.handleYgError(ctx, w, err) + return + } + err = y.yggdrasilService.SessionJoin(ctx, a, t, ip) + if err != nil { + y.handleYgError(ctx, w, err) + return + } + w.WriteHeader(204) + } +} + +func (y *Yggdrasil) HasJoined() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + name := r.FormValue("username") + serverId := r.FormValue("serverId") + ip := r.FormValue("ip") + if name == "" || serverId == "" { + y.logger.DebugContext(ctx, "name 或 serverID 为空") + w.WriteHeader(204) + return + } + u, err := y.yggdrasilService.HasJoined(ctx, name, serverId, ip, r.Host) + if err != nil { + y.logger.WarnContext(ctx, err.Error()) + w.WriteHeader(204) + } + w.Write(lo.Must(json.Marshal(u))) + } +} diff --git a/handle/yggdrasil/texture.go b/handle/yggdrasil/texture.go new file mode 100644 index 0000000..a805ce4 --- /dev/null +++ b/handle/yggdrasil/texture.go @@ -0,0 +1,77 @@ +package yggdrasil + +import ( + "context" + "errors" + "log/slog" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + + yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" +) + +func (y *Yggdrasil) getTokenbyAuthorization(ctx context.Context, w http.ResponseWriter, r *http.Request) string { + auth := r.Header.Get("Authorization") + if auth == "" { + y.logger.DebugContext(ctx, "缺少 Authorization") + w.WriteHeader(401) + return "" + } + al := strings.Split(auth, " ") + if len(al) != 2 || al[0] != "Bearer" { + y.logger.DebugContext(ctx, "Authorization 格式错误") + w.WriteHeader(401) + return "" + } + return al[1] +} + +func getUUIDbyParams(ctx context.Context, l *slog.Logger, w http.ResponseWriter) (string, string, bool) { + uuid := chi.URLParamFromCtx(ctx, "uuid") + textureType := chi.URLParamFromCtx(ctx, "textureType") + if uuid == "" { + l.DebugContext(ctx, "路径中缺少参数 uuid") + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "路径中缺少参数 uuid"}, 400) + return "", "", false + } + if textureType != "skin" && textureType != "cape" { + l.DebugContext(ctx, "上传类型错误") + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "上传类型错误"}, 400) + return "", "", false + + } + return uuid, textureType, true +} + +func (y *Yggdrasil) DelTexture() 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 + } + + err := y.yggdrasilService.DelTexture(ctx, t, textureType) + if err != nil { + if errors.Is(err, yggdrasilS.ErrUUIDNotEq) { + y.logger.DebugContext(ctx, err.Error()) + w.WriteHeader(401) + return + } + y.handleYgError(ctx, w, err) + return + } + w.WriteHeader(204) + } +} diff --git a/handle/yggdrasil/user.go b/handle/yggdrasil/user.go new file mode 100644 index 0000000..8ec254f --- /dev/null +++ b/handle/yggdrasil/user.go @@ -0,0 +1,172 @@ +package yggdrasil + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" +) + +func (y *Yggdrasil) Authenticate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cxt := r.Context() + a, has := getAnyModel[yggdrasil.Authenticate](cxt, w, r.Body, y.validate, y.logger) + if !has { + return + } + t, err := y.yggdrasilService.Authenticate(cxt, a) + if err != nil { + if errors.Is(err, yggdrasilS.ErrPassWord) || errors.Is(err, yggdrasilS.ErrRate) { + y.logger.DebugContext(cxt, err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: "Invalid credentials. Invalid username or password.", Error: "ForbiddenOperationException"}, 403) + return + } + y.handleYgError(cxt, w, err) + return + } + b, _ := json.Marshal(t) + w.Write(b) + } +} + +func (y *Yggdrasil) Validate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + } +} + +func (y *Yggdrasil) Signout() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cxt := r.Context() + a, has := getAnyModel[yggdrasil.Pass](cxt, w, r.Body, y.validate, y.logger) + if !has { + return + } + err := y.yggdrasilService.SignOut(cxt, a) + if err != nil { + if errors.Is(err, yggdrasilS.ErrPassWord) || errors.Is(err, yggdrasilS.ErrRate) { + y.logger.DebugContext(cxt, err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: "Invalid credentials. Invalid username or password.", Error: "ForbiddenOperationException"}, 403) + return + } + y.handleYgError(cxt, w, err) + return + } + w.WriteHeader(204) + } +} + +func (y *Yggdrasil) Invalidate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + cxt := r.Context() + + t := cxt.Value(tokenKey).(*model.TokenClaims) + + err := y.yggdrasilService.Invalidate(cxt, t) + if err != nil { + y.logger.WarnContext(cxt, err.Error()) + } + } +} + +func (y *Yggdrasil) Refresh() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cxt := r.Context() + token := cxt.Value(tokenKey).(*model.TokenClaims) + t, err := y.yggdrasilService.Refresh(cxt, token) + if err != nil { + y.handleYgError(cxt, w, err) + return + } + b, _ := json.Marshal(t) + w.Write(b) + } +} + +func (y *Yggdrasil) GetProfile() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + uuid := chi.URLParamFromCtx(ctx, "uuid") + + unsigned := r.FormValue("unsigned") + + unsignedBool := true + + switch unsigned { + case "true": + case "false": + unsignedBool = false + case "": + default: + y.logger.DebugContext(ctx, "unsigned 参数类型错误") + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "unsigned 参数类型错误"}, 400) + return + + } + u, err := y.yggdrasilService.GetProfile(ctx, uuid, unsignedBool, r.Host) + if err != nil { + if errors.Is(err, yggdrasilS.ErrNotUser) { + y.logger.DebugContext(ctx, err.Error()) + w.WriteHeader(204) + return + } + y.handleYgError(ctx, w, err) + return + } + b, _ := json.Marshal(u) + w.Write(b) + } +} + +func (y *Yggdrasil) BatchProfile() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + a, has := getAnyModel[[]string](ctx, w, r.Body, nil, y.logger) + if !has { + return + } + if len(a) > 5 { + y.logger.DebugContext(ctx, "最多同时查询五个") + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "最多同时查询五个"}, 400) + return + } + ul, err := y.yggdrasilService.BatchProfile(ctx, a) + if err != nil { + y.handleYgError(ctx, w, err) + return + } + w.Write(lo.Must1(json.Marshal(ul))) + } +} + +func (y *Yggdrasil) PlayerCertificates() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + t := ctx.Value(tokenKey).(*model.TokenClaims) + c, err := y.yggdrasilService.PlayerCertificates(ctx, t) + if err != nil { + y.handleYgError(ctx, w, err) + return + } + w.Write(lo.Must(json.Marshal(c))) + } +} + +func (y *Yggdrasil) PlayerAttributes() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"privileges":{"onlineChat":{"enabled":true},"multiplayerServer":{"enabled":true},"multiplayerRealms":{"enabled":true},"telemetry":{"enabled":true},"optionalTelemetry":{"enabled":true}},"profanityFilterPreferences":{"profanityFilterOn":true},"banStatus":{"bannedScopes":{}}}`)) + } +} + +func (y *Yggdrasil) PlayerReport() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "不准举报"}, 403) + } +} diff --git a/handle/yggdrasil/yggdrasil.go b/handle/yggdrasil/yggdrasil.go new file mode 100644 index 0000000..85895a6 --- /dev/null +++ b/handle/yggdrasil/yggdrasil.go @@ -0,0 +1,146 @@ +package yggdrasil + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "log/slog" + "net" + "net/http" + "net/url" + + "github.com/go-playground/validator/v10" + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/service/auth" + yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" + "github.com/xmdhs/authlib-skin/utils" +) + +type PubRsaKey string + +type Yggdrasil struct { + logger *slog.Logger + validate *validator.Validate + yggdrasilService *yggdrasilS.Yggdrasil + config config.Config + pubkey PubRsaKey +} + +func NewYggdrasil(logger *slog.Logger, validate *validator.Validate, yggdrasilService *yggdrasilS.Yggdrasil, config config.Config, pubkey PubRsaKey) *Yggdrasil { + return &Yggdrasil{ + logger: logger, + validate: validate, + yggdrasilService: yggdrasilService, + config: config, + pubkey: pubkey, + } +} + +func getAnyModel[K any](ctx context.Context, w http.ResponseWriter, r io.Reader, validate *validator.Validate, slog *slog.Logger) (K, bool) { + a, err := utils.DeCodeBody[K](r, validate) + if err != nil { + slog.DebugContext(ctx, err.Error()) + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: err.Error()}, 400) + return a, false + } + return a, true +} + +func (y *Yggdrasil) YggdrasilRoot() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var host string + if y.config.TextureBaseUrl != "" { + u := lo.Must(url.Parse(y.config.TextureBaseUrl)) + host = u.Hostname() + } else { + host, _ = lo.TryOr[string](func() (string, error) { + h, _, err := net.SplitHostPort(r.Host) + return h, err + }, r.Host) + } + homepage, _ := url.JoinPath(y.config.WebBaseUrl, "/login") + register, _ := url.JoinPath(y.config.WebBaseUrl, "/register") + + w.Write(lo.Must1(json.Marshal(yggdrasil.Yggdrasil{ + Meta: yggdrasil.YggdrasilMeta{ + ImplementationName: "authlib-skin", + ImplementationVersion: "0.0.1", + Links: yggdrasil.YggdrasilMetaLinks{ + Homepage: homepage, + Register: register, + }, + ServerName: y.config.ServerName, + EnableProfileKey: true, + }, + SignaturePublickey: string(y.pubkey), + SkinDomains: []string{host}, + }))) + + } +} + +func (y *Yggdrasil) TextureAssets() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "image/png") + http.StripPrefix("/texture/", http.FileServer(http.Dir(y.config.TexturePath))).ServeHTTP(w, r) + } +} + +type tokenValue string + +const tokenKey = tokenValue("token") + +func (y *Yggdrasil) Auth(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + bw := bytes.NewBuffer(nil) + tr := io.TeeReader(r.Body, bw) + a, err := utils.DeCodeBody[yggdrasil.ValidateToken](tr, y.validate) + if err != nil || a.AccessToken == "" { + token := y.getTokenbyAuthorization(ctx, w, r) + if token == "" { + return + } + a.AccessToken = token + } + r.Body = readerClose{r: io.MultiReader(bw, r.Body), close: r.Body} + + t, err := y.yggdrasilService.Auth(ctx, a) + if err != nil { + if errors.Is(err, auth.ErrTokenInvalid) { + y.logger.DebugContext(ctx, err.Error()) + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403) + return + } + y.handleYgError(ctx, w, err) + return + } + r = r.WithContext(context.WithValue(ctx, tokenKey, t)) + handle.ServeHTTP(w, r) + }) +} + +type readerClose struct { + r io.Reader + close io.Closer +} + +func (r readerClose) Read(p []byte) (n int, err error) { + return r.r.Read(p) +} + +func (r readerClose) Close() error { + return r.close.Close() +} + +func (y *Yggdrasil) PublicKeys() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + p := y.yggdrasilService.PublicKeys(ctx) + json.NewEncoder(w).Encode(p) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..38a196c --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "errors" + "flag" + "fmt" + "os" + + _ "embed" + + "github.com/pelletier/go-toml/v2" + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/server" + "github.com/xmdhs/authlib-skin/utils/sign" +) + +var configPath string + +func init() { + flag.StringVar(&configPath, "c", "config.toml", "") + flag.Parse() +} + +func main() { + ctx := context.Background() + + if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) { + rsa2048 := lo.Must(rsa.GenerateKey(rand.Reader, 4096)) + as := sign.NewAuthlibSignWithKey(rsa2048) + + c := config.Default() + c.RsaPriKey = lo.Must(as.GetPriKey()) + + lo.Must0(os.WriteFile(configPath, lo.Must(toml.Marshal(c)), 0600)) + fmt.Println("未找到配置文件,已写入模板配置文件") + } + + b, err := os.ReadFile(configPath) + if err != nil { + panic(err) + } + var config config.Config + lo.Must0(toml.Unmarshal(b, &config)) + s, cancel := lo.Must2(server.InitializeRoute(ctx, config)) + defer cancel() + panic(s.ListenAndServe()) +} diff --git a/model/const.go b/model/const.go new file mode 100644 index 0000000..19711b6 --- /dev/null +++ b/model/const.go @@ -0,0 +1,19 @@ +package model + +type APIStatus int + +const ( + ErrUnknown APIStatus = iota - 1 + OK + ErrInput + ErrService + ErrExistUser + ErrRegLimit + ErrAuth + ErrPassWord + ErrExitsName + ErrNotAdmin + ErrUserDisable + ErrCaptcha + ErrEmailSend +) diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..a430b85 --- /dev/null +++ b/model/model.go @@ -0,0 +1,102 @@ +package model + +import "github.com/golang-jwt/jwt/v5" + +type API[T any] struct { + Code APIStatus `json:"code"` + Data T `json:"data"` + Msg string `json:"msg,omitempty"` +} + +type List[T any] struct { + Total int `json:"total"` + List []T `json:"list"` +} + +type UserReg struct { + Email string `validate:"required,email"` + Password string `validate:"required,min=6,max=50"` + Name string `validate:"required,min=3,max=16"` + CaptchaToken string + EmailJwt string +} + +type TokenClaims struct { + // token id 验证 token 是否过期 + Tid string `json:"tid"` + // ClientToken Yggdrasil 协议中使用 + CID string `json:"cid"` + // 用户 id + UID int `json:"uid"` + jwt.RegisteredClaims +} + +type Captcha struct { + Type string `json:"type"` + SiteKey string `json:"siteKey"` +} + +type UserInfo struct { + UID int `json:"uid"` + UUID string `json:"uuid"` + IsAdmin bool `json:"is_admin"` +} + +type ChangePasswd struct { + Old string `json:"old"` + New string `json:"new" validate:"required,min=6,max=50"` +} + +type UserList struct { + UserInfo + Email string `json:"email"` + RegIp string `json:"reg_ip"` + Name string `json:"name"` + IsDisable bool `json:"is_disable"` +} + +type ChangeName struct { + Name string `json:"name" validate:"required,min=3,max=16"` +} + +type Config struct { + Captcha Captcha `json:"captcha"` + AllowChangeName bool + ServerName string `json:"serverName"` + NeedEmail bool + AllowDomain []string + EmailReg string + EmailRegMsg string +} + +type EditUser struct { + Email string `json:"email" validate:"omitempty,email"` + Name string `json:"name" validate:"omitempty,min=3,max=16"` + Password string `json:"password"` + IsAdmin *bool `json:"is_admin"` + IsDisable *bool `json:"is_disable"` + DelTextures bool `json:"del_textures"` +} + +type Login struct { + Email string `json:"email" validate:"required,email"` + Password string `json:"password"` + CaptchaToken string +} + +type LoginRep struct { + Token string `json:"token"` + Name string `json:"name"` + UUID string `json:"uuid"` +} + +type SendRegEmail struct { + Email string `json:"email" validate:"required,email"` + CaptchaToken string `json:"captchaToken"` +} + +type ForgotPassword struct { + Email string `json:"email" validate:"required,email"` + EmailJwt string `json:"emailJwt" validate:"required"` + PassWord string `json:"passWord" validate:"required"` +} diff --git a/model/yggdrasil/model.go b/model/yggdrasil/model.go new file mode 100644 index 0000000..4ccc59b --- /dev/null +++ b/model/yggdrasil/model.go @@ -0,0 +1,103 @@ +package yggdrasil + +type Pass struct { + // 目前只能是 email + Username string `json:"username" validate:"required,email"` + Password string `json:"password" validate:"required"` +} + +type Authenticate struct { + ClientToken string `json:"clientToken"` + RequestUser bool `json:"requestUser"` + Pass +} + +type Error struct { + Cause string `json:"cause,omitempty"` + Error string `json:"error,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` +} + +type TokenUserID struct { + ID string `json:"id"` + Properties []any `json:"properties,omitempty"` +} + +type Token struct { + AccessToken string `json:"accessToken"` + AvailableProfiles []UserInfo `json:"availableProfiles,omitempty"` + ClientToken string `json:"clientToken"` + SelectedProfile UserInfo `json:"selectedProfile"` + User TokenUserID `json:"user,omitempty"` +} + +type ValidateToken struct { + // jwt + AccessToken string `json:"accessToken" validate:"required,jwt"` + ClientToken string `json:"clientToken"` +} + +type RefreshToken struct { + ValidateToken + RequestUser bool `json:"requestUser"` + SelectedProfile UserInfo `json:"selectedProfile"` +} + +type UserInfo struct { + ID string `json:"id"` + Name string `json:"name"` + Properties []UserProperties `json:"properties,omitempty"` +} + +type UserProperties struct { + Name string `json:"name"` + Value string `json:"value"` + Signature string `json:"signature,omitempty"` +} + +type Session struct { + AccessToken string `json:"accessToken" validate:"required,jwt"` + SelectedProfile string `json:"selectedProfile" validate:"required"` + ServerID string `json:"serverId"` +} + +type Yggdrasil struct { + Meta YggdrasilMeta `json:"meta"` + SignaturePublickey string `json:"signaturePublickey"` + SkinDomains []string `json:"skinDomains"` +} + +type YggdrasilMeta struct { + ImplementationName string `json:"implementationName"` + ImplementationVersion string `json:"implementationVersion"` + Links YggdrasilMetaLinks `json:"links"` + ServerName string `json:"serverName"` + EnableProfileKey bool `json:"feature.enable_profile_key"` +} + +type YggdrasilMetaLinks struct { + Homepage string `json:"homepage"` + Register string `json:"register"` +} + +type Certificates struct { + ExpiresAt string `json:"expiresAt"` + KeyPair CertificatesKeyPair `json:"keyPair"` + PublicKeySignature string `json:"publicKeySignature"` + PublicKeySignatureV2 string `json:"publicKeySignatureV2"` + RefreshedAfter string `json:"refreshedAfter"` +} + +type CertificatesKeyPair struct { + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` +} + +type PublicKeys struct { + PlayerCertificateKeys []PublicKeyList `json:"playerCertificateKeys"` + ProfilePropertyKeys []PublicKeyList `json:"profilePropertyKeys"` +} + +type PublicKeyList struct { + PublicKey string `json:"publicKey"` +} diff --git a/model/yggdrasil/textures.go b/model/yggdrasil/textures.go new file mode 100644 index 0000000..fe04f1d --- /dev/null +++ b/model/yggdrasil/textures.go @@ -0,0 +1,28 @@ +package yggdrasil + +import ( + "encoding/base64" + "encoding/json" +) + +type UserTextures struct { + // UUID + ProfileID string `json:"profileId"` + ProfileName string `json:"profileName"` + Textures map[string]Textures `json:"textures"` + // 时间戳 毫秒 + Timestamp int64 `json:"timestamp"` +} + +type Textures struct { + Url string `json:"url"` + Metadata map[string]string `json:"metadata"` +} + +func (u UserTextures) Base64() string { + b, err := json.Marshal(u) + if err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(b) +} diff --git a/server/provide.go b/server/provide.go new file mode 100644 index 0000000..74ed9c2 --- /dev/null +++ b/server/provide.go @@ -0,0 +1,114 @@ +package server + +import ( + "context" + "crypto/rsa" + "database/sql" + "fmt" + "log/slog" + "net/http" + "os" + "time" + + entsql "entgo.io/ent/dialect/sql" + "github.com/go-playground/validator/v10" + _ "github.com/go-sql-driver/mysql" + "github.com/google/wire" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/migrate" + "github.com/xmdhs/authlib-skin/handle/yggdrasil" + "github.com/xmdhs/authlib-skin/utils/sign" +) + +func ProvideSlog(c config.Config) slog.Handler { + var level slog.Level + switch c.Log.Level { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + level = slog.LevelDebug + } + o := &slog.HandlerOptions{Level: level} + + var h slog.Handler + if c.Log.Json { + h = slog.NewJSONHandler(os.Stderr, o) + } else { + h = slog.NewTextHandler(os.Stderr, o) + } + + return h +} + +func ProvideDB(c config.Config) (*sql.DB, func(), error) { + db, err := sql.Open(c.Sql.DriverName, c.Sql.Dsn) + if err != nil { + return nil, nil, fmt.Errorf("ProvideDB: %w", err) + } + db.SetMaxIdleConns(10) + db.SetConnMaxIdleTime(2 * time.Minute) + return db, func() { db.Close() }, nil +} + +func ProvideEnt(ctx context.Context, db *sql.DB, c config.Config, sl *slog.Logger) (*ent.Client, func(), error) { + drv := entsql.OpenDB(c.Sql.DriverName, db) + opts := []ent.Option{ent.Driver(drv), ent.Log( + func(a ...any) { + sl.Debug(fmt.Sprint(a)) + }, + )} + if c.Debug { + opts = append(opts, ent.Debug()) + } + e := ent.NewClient(opts...) + err := e.Schema.Create(ctx, migrate.WithForeignKeys(false), migrate.WithDropIndex(true), migrate.WithDropColumn(true)) + if err != nil { + return nil, nil, fmt.Errorf("ProvideEnt: %w", err) + } + return e, func() { e.Close() }, nil +} + +func ProvideValidate() *validator.Validate { + return validator.New() +} + +func ProvideCache(c config.Config) cache.Cache { + if c.Cache.Type == "redis" { + return cache.NewRedis(c.Cache.Addr, c.Cache.Password) + } + return cache.NewFastCache(c.Cache.Ram) +} + +func ProvidePriKey(c config.Config) (*rsa.PrivateKey, error) { + a, err := sign.NewAuthlibSign([]byte(c.RsaPriKey)) + if err != nil { + return nil, fmt.Errorf("ProvidePriKey: %w", err) + } + return a.GetKey(), nil +} + +func ProvidePubKeyStr(pri *rsa.PrivateKey) (yggdrasil.PubRsaKey, error) { + s, err := sign.NewAuthlibSignWithKey(pri).GetPKIXPubKeyWithOutRsa() + if err != nil { + return "", fmt.Errorf("ProvidePubKey: %w", err) + } + return yggdrasil.PubRsaKey(s), nil +} + +func ProvidePubKey(pri *rsa.PrivateKey) *rsa.PublicKey { + return &pri.PublicKey +} + +func ProvideHttpClient() *http.Client { + return &http.Client{} +} + +var Set = wire.NewSet(ProvideSlog, ProvideDB, ProvideEnt, ProvideValidate, ProvideCache, ProvidePriKey, ProvidePubKey, ProvidePubKeyStr, ProvideHttpClient) diff --git a/server/route/middleware.go b/server/route/middleware.go new file mode 100644 index 0000000..9a10c33 --- /dev/null +++ b/server/route/middleware.go @@ -0,0 +1,82 @@ +package route + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "time" + + "github.com/go-chi/chi/v5/middleware" +) + +func warpHJSON(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + handle.ServeHTTP(w, r) + }) +} + +func NewStructuredLogger(handler slog.Handler) func(next http.Handler) http.Handler { + return middleware.RequestLogger(&StructuredLogger{Logger: handler}) +} + +type StructuredLogger struct { + Logger slog.Handler +} + +func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { + var logFields []slog.Attr + logFields = append(logFields, slog.String("ts", time.Now().UTC().Format(time.RFC1123))) + ctx := r.Context() + + if reqID := middleware.GetReqID(ctx); reqID != "" { + logFields = append(logFields, slog.String("req_id", reqID)) + } + + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + + handler := l.Logger.WithAttrs(append(logFields, + slog.String("http_scheme", scheme), + slog.String("http_proto", r.Proto), + slog.String("http_method", r.Method), + slog.String("remote_addr", r.RemoteAddr), + slog.String("user_agent", r.UserAgent()), + slog.String("uri", fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)))) + + entry := StructuredLoggerEntry{Logger: slog.New(handler), ctx: ctx} + + entry.Logger.LogAttrs(ctx, slog.LevelDebug, "request started") + + return &entry +} + +type StructuredLoggerEntry struct { + Logger *slog.Logger + ctx context.Context +} + +func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) { + l.Logger.LogAttrs(l.ctx, slog.LevelDebug, "request complete", + slog.Int("resp_status", status), + slog.Int("resp_byte_length", bytes), + slog.Float64("resp_elapsed_ms", float64(elapsed.Nanoseconds())/1000000.0), + ) +} + +func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) { + l.Logger.LogAttrs(l.ctx, slog.LevelDebug, "", + slog.String("stack", string(stack)), + slog.String("panic", fmt.Sprintf("%+v", v)), + ) +} + +func APILocationIndication(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Authlib-Injector-API-Location", "/api/yggdrasil/") + handle.ServeHTTP(w, r) + }) +} diff --git a/server/route/route.go b/server/route/route.go new file mode 100644 index 0000000..0fef661 --- /dev/null +++ b/server/route/route.go @@ -0,0 +1,109 @@ +package route + +import ( + "context" + "log/slog" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/cors" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/handle" + "github.com/xmdhs/authlib-skin/handle/yggdrasil" + "github.com/xmdhs/authlib-skin/server/static" +) + +func NewRoute(handelY *yggdrasil.Yggdrasil, handel *handle.Handel, c config.Config, sl slog.Handler, + userHandel *handle.UserHandel, adminHandel *handle.AdminHandel) http.Handler { + r := chi.NewRouter() + r.Use(middleware.RequestID) + if c.RaelIP { + r.Use(middleware.RealIP) + } + if sl.Enabled(context.Background(), slog.LevelDebug) { + r.Use(NewStructuredLogger(sl)) + } + r.Use(middleware.Recoverer) + r.Use(cors.AllowAll().Handler) + r.Use(APILocationIndication) + + r.Mount("/", static.StaticServer()) + r.Mount("/api/v1", newSkinApi(handel, userHandel, adminHandel)) + r.Mount("/api/yggdrasil", newYggdrasil(handelY)) + + if c.Debug { + r.Mount("/debug", middleware.Profiler()) + } + + r.Get("/texture/*", handelY.TextureAssets()) + + return r +} + +func newYggdrasil(handelY *yggdrasil.Yggdrasil) http.Handler { + r := chi.NewRouter() + r.Use(warpHJSON) + + r.Group(func(r chi.Router) { + r.Use(handelY.Auth) + r.Post("/authserver/validate", handelY.Validate()) + r.Post("/authserver/invalidate", handelY.Invalidate()) + r.Post("/authserver/refresh", handelY.Refresh()) + + r.Delete("/api/user/profile/{uuid}/{textureType}", handelY.DelTexture()) + + r.Post("/sessionserver/session/minecraft/join", handelY.SessionJoin()) + r.Post("/minecraftservices/player/certificates", handelY.PlayerCertificates()) + + }) + + r.Post("/authserver/authenticate", handelY.Authenticate()) + r.Post("/authserver/signout", handelY.Signout()) + + r.Get("/sessionserver/session/minecraft/profile/{uuid}", handelY.GetProfile()) + r.Post("/api/profiles/minecraft", handelY.BatchProfile()) + r.Post("/minecraftservices/minecraft/profile/lookup/bulk/byname", handelY.BatchProfile()) // 23w42a + + r.Get("/sessionserver/session/minecraft/hasJoined", handelY.HasJoined()) + + r.Get("/minecraftservices/player/attributes", handelY.PlayerAttributes()) + r.Post("/minecraftservices/player/attributes", handelY.PlayerAttributes()) + r.Post("/minecraftservices/player/report", handelY.PlayerReport()) + r.Get("/minecraftservices/publickeys", handelY.PublicKeys()) + + r.Get("/", handelY.YggdrasilRoot()) + return r +} + +func newSkinApi(handel *handle.Handel, userHandel *handle.UserHandel, adminHandel *handle.AdminHandel) http.Handler { + r := chi.NewRouter() + + r.Post("/user/reg", userHandel.Reg()) + r.Post("/user/login", userHandel.Login()) + r.Get("/config", handel.GetConfig()) + + r.Group(func(r chi.Router) { + r.Use(userHandel.NeedEnableEmail) + r.Post("/user/reg_email", userHandel.SendRegEmail()) + r.Post("/user/forgot_email", userHandel.SendForgotEmail()) + r.Post("/user/forgot", userHandel.ForgotPassword()) + }) + + r.Group(func(r chi.Router) { + r.Use(adminHandel.NeedAuth) + r.Get("/user", userHandel.UserInfo()) + r.Post("/user/password", userHandel.ChangePasswd()) + r.Post("/user/name", userHandel.ChangeName()) + r.Put("/user/skin/{textureType}", userHandel.PutTexture()) + }) + + r.Group(func(r chi.Router) { + r.Use(adminHandel.NeedAuth) + r.Use(adminHandel.NeedAdmin) + r.Get("/admin/users", adminHandel.ListUser()) + r.Patch("/admin/user/{uid}", adminHandel.EditUser()) + }) + + return r +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..9118611 --- /dev/null +++ b/server/server.go @@ -0,0 +1,19 @@ +package server + +import ( + "net/http" + "time" + + "github.com/xmdhs/authlib-skin/config" +) + +func NewServer(c config.Config, route http.Handler) (*http.Server, func()) { + s := &http.Server{ + ReadTimeout: 10 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 20 * time.Second, + Addr: c.Port, + Handler: route, + } + return s, func() { s.Close() } +} diff --git a/server/slog.go b/server/slog.go new file mode 100644 index 0000000..e75f68a --- /dev/null +++ b/server/slog.go @@ -0,0 +1,27 @@ +package server + +import ( + "context" + "log/slog" + + "github.com/go-chi/chi/v5/middleware" +) + +type warpSlogHandle struct { + slog.Handler +} + +func (w *warpSlogHandle) Handle(ctx context.Context, r slog.Record) error { + id := middleware.GetReqID(ctx) + if id != "" { + r.AddAttrs(slog.String("trackID", id)) + } + return w.Handler.Handle(ctx, r) +} + +func NewSlog(h slog.Handler) *slog.Logger { + l := slog.New(&warpSlogHandle{ + Handler: h, + }) + return l +} diff --git a/server/sqlite_init.go b/server/sqlite_init.go new file mode 100644 index 0000000..cb937d7 --- /dev/null +++ b/server/sqlite_init.go @@ -0,0 +1,5 @@ +//go:build sqlite + +package server + +import _ "github.com/mattn/go-sqlite3" diff --git a/server/static/static.go b/server/static/static.go new file mode 100644 index 0000000..5805130 --- /dev/null +++ b/server/static/static.go @@ -0,0 +1,36 @@ +package static + +import ( + "embed" + "io/fs" + "net/http" + + "github.com/go-chi/chi/v5" +) + +//go:embed files +var staticFs embed.FS + +func StaticServer() http.Handler { + serverRoot, err := fs.Sub(staticFs, "files") + if err != nil { + panic(err) + } + + r := chi.NewRouter() + + r.Get("/", index) + r.Get("/*", index) + + r.Mount("/assets", http.FileServer(http.FS(serverRoot))) + + return r +} + +func index(w http.ResponseWriter, r *http.Request) { + b, err := staticFs.ReadFile("files/index.html") + if err != nil { + panic(err) + } + w.Write(b) +} diff --git a/server/wire.go b/server/wire.go new file mode 100644 index 0000000..f183ab6 --- /dev/null +++ b/server/wire.go @@ -0,0 +1,32 @@ +//go:build wireinject + +package server + +import ( + "context" + "net/http" + + "github.com/google/wire" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/handle" + "github.com/xmdhs/authlib-skin/handle/handelerror" + "github.com/xmdhs/authlib-skin/handle/yggdrasil" + "github.com/xmdhs/authlib-skin/server/route" + "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" + "github.com/xmdhs/authlib-skin/service/email" + yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" +) + +var serviceSet = wire.NewSet(service.Service, yggdrasilS.NewYggdrasil, email.NewEmail, auth.NewAuthService, + captcha.NewCaptchaService, +) + +var handleSet = wire.NewSet(handelerror.NewHandleError, handle.HandelSet, yggdrasil.NewYggdrasil) + +func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) { + panic(wire.Build(Set, route.NewRoute, NewSlog, + NewServer, handleSet, serviceSet, + )) +} diff --git a/server/wire_gen.go b/server/wire_gen.go new file mode 100644 index 0000000..7478f20 --- /dev/null +++ b/server/wire_gen.go @@ -0,0 +1,90 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package server + +import ( + "context" + "github.com/google/wire" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/handle" + "github.com/xmdhs/authlib-skin/handle/handelerror" + yggdrasil2 "github.com/xmdhs/authlib-skin/handle/yggdrasil" + "github.com/xmdhs/authlib-skin/server/route" + "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" + "github.com/xmdhs/authlib-skin/service/email" + "github.com/xmdhs/authlib-skin/service/yggdrasil" + "net/http" +) + +import ( + _ "github.com/go-sql-driver/mysql" +) + +// Injectors from wire.go: + +func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) { + handler := ProvideSlog(c) + logger := NewSlog(handler) + validate := ProvideValidate() + db, cleanup, err := ProvideDB(c) + if err != nil { + return nil, nil, err + } + client, cleanup2, err := ProvideEnt(ctx, db, c, logger) + if err != nil { + cleanup() + return nil, nil, err + } + cache := ProvideCache(c) + privateKey, err := ProvidePriKey(c) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + publicKey := ProvidePubKey(privateKey) + authService := auth.NewAuthService(client, cache, publicKey, privateKey) + yggdrasilYggdrasil := yggdrasil.NewYggdrasil(client, cache, c, privateKey, authService) + pubRsaKey, err := ProvidePubKeyStr(privateKey) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + yggdrasil3 := yggdrasil2.NewYggdrasil(logger, validate, yggdrasilYggdrasil, c, pubRsaKey) + webService := service.NewWebService(c) + handel := handle.NewHandel(webService) + handleError := handelerror.NewHandleError(logger) + httpClient := ProvideHttpClient() + captchaService := captcha.NewCaptchaService(c, httpClient) + emailService, err := email.NewEmail(privateKey, c, cache) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + userService := service.NewUserSerice(c, client, captchaService, authService, cache, emailService) + textureService := service.NewTextureService(client, c, cache) + userHandel := handle.NewUserHandel(handleError, validate, userService, logger, textureService, c) + adminService := service.NewAdminService(authService, client, c, cache) + adminHandel := handle.NewAdminHandel(handleError, adminService, validate) + httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel) + server, cleanup3 := NewServer(c, httpHandler) + return server, func() { + cleanup3() + cleanup2() + cleanup() + }, nil +} + +// wire.go: + +var serviceSet = wire.NewSet(service.Service, yggdrasil.NewYggdrasil, email.NewEmail, auth.NewAuthService, captcha.NewCaptchaService) + +var handleSet = wire.NewSet(handelerror.NewHandleError, handle.HandelSet, yggdrasil2.NewYggdrasil) diff --git a/service/admin.go b/service/admin.go new file mode 100644 index 0000000..8d98a67 --- /dev/null +++ b/service/admin.go @@ -0,0 +1,201 @@ +package service + +import ( + "context" + "errors" + "fmt" + "strconv" + + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/predicate" + "github.com/xmdhs/authlib-skin/db/ent/user" + "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/yggdrasil" + "github.com/xmdhs/authlib-skin/service/auth" + utilsService "github.com/xmdhs/authlib-skin/service/utils" + "github.com/xmdhs/authlib-skin/utils" +) + +type AdminService struct { + authService *auth.AuthService + client *ent.Client + config config.Config + cache cache.Cache +} + +func NewAdminService(authService *auth.AuthService, client *ent.Client, + config config.Config, cache cache.Cache) *AdminService { + return &AdminService{ + authService: authService, + client: client, + config: config, + cache: cache, + } +} + +var ErrNotAdmin = errors.New("无权限") + +func (w *AdminService) Auth(ctx context.Context, token string) (*model.TokenClaims, error) { + t, err := w.authService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, false) + if err != nil { + return nil, fmt.Errorf("WebService.Auth: %w", err) + } + return t, nil +} + +func (w *AdminService) IsAdmin(ctx context.Context, t *model.TokenClaims) error { + u, err := w.client.User.Query().Where(user.ID(t.UID)).First(ctx) + if err != nil { + return fmt.Errorf("IsAdmin: %w", err) + } + if !auth.IsAdmin(u.State) { + return fmt.Errorf("IsAdmin: %w", ErrNotAdmin) + } + return nil +} + +func (w *AdminService) ListUser(ctx context.Context, page int, email, name string) ([]model.UserList, int, error) { + whereL := []predicate.User{} + if email != "" { + whereL = append(whereL, user.EmailHasPrefix(email)) + } + if name != "" { + whereL = append(whereL, user.HasProfileWith(userprofile.NameHasPrefix(name))) + } + u, err := w.client.User.Query().WithProfile(). + Where(user.And(whereL...)). + Limit(20).Offset((page - 1) * 20).All(ctx) + if err != nil { + return nil, 0, fmt.Errorf("ListUser: %w", err) + } + ul := make([]model.UserList, 0, len(u)) + + for _, v := range u { + if v.Edges.Profile == nil { + continue + } + ul = append(ul, model.UserList{ + UserInfo: model.UserInfo{ + UID: v.ID, + UUID: v.Edges.Profile.UUID, + IsAdmin: auth.IsAdmin(v.State), + }, + Email: v.Email, + RegIp: v.RegIP, + Name: v.Edges.Profile.Name, + IsDisable: auth.IsDisable(v.State), + }) + } + + uc, err := w.client.User.Query().Where(user.And(whereL...)).Count(ctx) + if err != nil { + return nil, 0, fmt.Errorf("ListUser: %w", err) + } + return ul, uc, nil +} + +func (w *AdminService) EditUser(ctx context.Context, u model.EditUser, uid int) error { + uuid := "" + changePasswd := false + err := utils.WithTx(ctx, w.client, func(tx *ent.Tx) error { + upUser := tx.User.UpdateOneID(uid) + + if u.Email != "" { + c, err := tx.User.Query().Where(user.Email(u.Email)).Count(ctx) + if err != nil { + return err + } + if c != 0 { + return ErrExistUser + } + upUser = upUser.SetEmail(u.Email) + } + + if u.Name != "" { + 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 { + userProfile, err := tx.UserProfile.Query().Where(userprofile.ID(uid)).First(ctx) + if err != nil { + return err + } + uuid = userProfile.UUID + tl := []string{"skin", "cape"} + for _, v := range tl { + err := utilsService.DelTexture(ctx, userProfile.ID, v, tx.Client(), w.config.TexturePath) + if err != nil { + return err + } + } + } + + aUser, err := tx.User.Get(ctx, uid) + if err != nil { + return err + } + + state := aUser.State + if u.IsAdmin != nil { + state = auth.SetAdmin(state, *u.IsAdmin) + } + if u.IsDisable != nil { + if *u.IsDisable { + changePasswd = true + } + state = auth.SetDisable(state, *u.IsDisable) + } + if state != aUser.State { + upUser = upUser.SetState(state) + } + if u.Password != "" { + pass, salt := utils.Argon2ID(u.Password) + upUser = upUser.SetPassword(pass).SetSalt(salt) + changePasswd = true + } + + err = upUser.Exec(ctx) + if err != nil { + return err + } + + if changePasswd { + err = tx.UserToken.Update().Where(usertoken.HasUserWith(user.ID(uid))).AddTokenID(1).Exec(ctx) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return fmt.Errorf("EditUser: %w", err) + } + if uuid != "" { + err = w.cache.Del([]byte("Profile" + uuid)) + if err != nil { + return fmt.Errorf("EditUser: %w", err) + } + } + if changePasswd { + err = w.cache.Del([]byte("auth" + strconv.Itoa(uid))) + if err != nil { + return fmt.Errorf("EditUser: %w", err) + } + } + return nil +} diff --git a/service/admin_test.go b/service/admin_test.go new file mode 100644 index 0000000..144f987 --- /dev/null +++ b/service/admin_test.go @@ -0,0 +1,65 @@ +package service + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/xmdhs/authlib-skin/model" +) + +func TestAdminSerice_Auth(t *testing.T) { + ctx := context.Background() + lr, err := userSerice.Reg(ctx, model.UserReg{ + Email: "TestWebService_Auth@xmdhs.com", + Password: "TestWebService_Auth", + Name: "TestWebService_Auth", + CaptchaToken: "", + }, "127.0.1.0/24", "127.0.1.0") + require.Nil(t, err) + require.Equal(t, lr.Name, "TestWebService_Auth") + + token, err := adminSerice.Auth(ctx, lr.Token) + require.Nil(t, err) + + assert.Equal(t, token.Subject, lr.UUID) + assert.Equal(t, token.Tid, "1") + + type args struct { + ctx context.Context + token string + } + tests := []struct { + name string + w *AdminService + args args + wantErr bool + }{ + { + name: "some string", + w: adminSerice, + args: args{ + ctx: ctx, + token: "123213", + }, + wantErr: true, + }, + { + name: "valid jwt", + w: adminSerice, + args: args{ + ctx: ctx, + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbeyJ0b29sdHQiOiJodHRwczovL3Rvb2x0dC5jb20ifV0sImlhdCI6MTY5NzEwMjMzOCwiZXhwIjoxNjk3MTI2Mzk5LCJhdWQiOiIiLCJpc3MiOiIiLCJzdWIiOiIifQ.JTQWl1PEX8u7PhVc4dTtv1DRS6e1PbMDZNWOAFJmVqE", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _, err := tt.w.Auth(tt.args.ctx, tt.args.token); (err != nil) != tt.wantErr { + t.Errorf("WebService.Auth() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/service/auth/auth.go b/service/auth/auth.go new file mode 100644 index 0000000..db40c0e --- /dev/null +++ b/service/auth/auth.go @@ -0,0 +1,141 @@ +package auth + +import ( + "context" + "crypto/rsa" + "errors" + "fmt" + "strconv" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/utils" +) + +type AuthService struct { + client *ent.Client + c cache.Cache + pub *rsa.PublicKey + pri *rsa.PrivateKey +} + +func NewAuthService( + client *ent.Client, + c cache.Cache, + pub *rsa.PublicKey, + pri *rsa.PrivateKey, +) *AuthService { + return &AuthService{ + client: client, + c: c, + pub: pub, + pri: pri, + } +} + +var ( + ErrTokenInvalid = errors.New("token 无效") + ErrUserDisable = errors.New("用户被禁用") +) + +func (a *AuthService) Auth(ctx context.Context, t yggdrasil.ValidateToken, tmpInvalid bool) (*model.TokenClaims, error) { + token, err := jwt.ParseWithClaims(t.AccessToken, &model.TokenClaims{}, func(t *jwt.Token) (interface{}, error) { + return a.pub, nil + }) + if err != nil { + return nil, fmt.Errorf("Auth: %w", errors.Join(err, ErrTokenInvalid)) + } + + claims, ok := token.Claims.(*model.TokenClaims) + if !ok || !token.Valid { + return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + if t.ClientToken != "" && t.ClientToken != claims.CID { + return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + + if tmpInvalid { + it, err := claims.GetIssuedAt() + if err != nil { + return nil, fmt.Errorf("Auth: %w", errors.Join(err, ErrTokenInvalid)) + } + et, err := claims.GetExpirationTime() + if err != nil { + return nil, fmt.Errorf("Auth: %w", errors.Join(err, ErrTokenInvalid)) + } + invalidTime := it.Add(et.Time.Sub(it.Time) / 2) + if time.Now().After(invalidTime) { + return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + } + tokenID, err := func() (uint64, error) { + c := cache.CacheHelp[uint64]{Cache: a.c} + key := []byte("auth" + strconv.Itoa(claims.UID)) + t, err := c.Get(key) + if err != nil { + return 0, err + } + if t != 0 { + return t, nil + } + ut, err := a.client.UserToken.Query().Where(usertoken.HasUserWith(user.ID(claims.UID))).First(ctx) + if err != nil { + var ne *ent.NotFoundError + if errors.As(err, &ne) { + return 0, ErrTokenInvalid + } + return 0, err + } + return ut.TokenID, c.Put(key, ut.TokenID, time.Now().Add(20*time.Minute)) + }() + if err != nil { + return nil, fmt.Errorf("Auth: %w", err) + } + if strconv.FormatUint(tokenID, 10) != claims.Tid { + return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + return claims, nil +} + +func (a *AuthService) CreateToken(ctx context.Context, u *ent.User, clientToken string, uuid string) (string, error) { + if IsDisable(u.State) { + return "", fmt.Errorf("CreateToken: %w", ErrUserDisable) + } + var utoken *ent.UserToken + err := utils.WithTx(ctx, a.client, func(tx *ent.Tx) error { + var err error + utoken, err = tx.User.QueryToken(u).ForUpdateA().First(ctx) + if err != nil { + var nf *ent.NotFoundError + if !errors.As(err, &nf) { + return err + } + } + if utoken == nil { + ut, err := tx.UserToken.Create().SetTokenID(1).SetUser(u).Save(ctx) + if err != nil { + return err + } + utoken = ut + } + return nil + }) + if err != nil { + return "", fmt.Errorf("CreateToken: %w", err) + } + err = a.c.Del([]byte("auth" + strconv.Itoa(u.ID))) + if err != nil { + return "", fmt.Errorf("CreateToken: %w", err) + } + t, err := NewJwtToken(a.pri, strconv.FormatUint(utoken.TokenID, 10), clientToken, uuid, u.ID) + if err != nil { + return "", fmt.Errorf("CreateToken: %w", err) + } + return t, nil +} diff --git a/service/auth/utils.go b/service/auth/utils.go new file mode 100644 index 0000000..feb5c7d --- /dev/null +++ b/service/auth/utils.go @@ -0,0 +1,52 @@ +package auth + +import ( + "crypto/rsa" + "fmt" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/xmdhs/authlib-skin/model" +) + +func IsAdmin(state int) bool { + return state&1 == 1 +} + +func IsDisable(state int) bool { + return state&2 == 2 +} + +func SetAdmin(state int, is bool) int { + if is { + return state | 1 + } + return state & (state ^ 1) +} + +func SetDisable(state int, is bool) int { + if is { + return state | 2 + } + return state & (state ^ 2) +} + +func NewJwtToken(jwtKey *rsa.PrivateKey, tokenID, clientToken, UUID string, userID int) (string, error) { + claims := model.TokenClaims{ + Tid: tokenID, + CID: clientToken, + UID: userID, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * 24 * time.Hour)), + Issuer: "authlib-skin", + Subject: UUID, + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + jwts, err := token.SignedString(jwtKey) + if err != nil { + return "", fmt.Errorf("NewJwtToken: %w", err) + } + return jwts, nil +} diff --git a/service/captcha/captcha.go b/service/captcha/captcha.go new file mode 100644 index 0000000..606cc1c --- /dev/null +++ b/service/captcha/captcha.go @@ -0,0 +1,85 @@ +package captcha + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/xmdhs/authlib-skin/config" +) + +type CaptchaService struct { + config config.Config + httpClient *http.Client +} + +func NewCaptchaService(config config.Config, httpClient *http.Client) *CaptchaService { + return &CaptchaService{ + config: config, + httpClient: httpClient, + } +} + +func (c *CaptchaService) VerifyCaptcha(ctx context.Context, token, ip string) error { + if c.config.Captcha.Type != "turnstile" { + return nil + } + bw := &bytes.Buffer{} + err := json.NewEncoder(bw).Encode(turnstileResponse{ + Secret: c.config.Captcha.Secret, + Response: token, + Remoteip: ip, + }) + if err != nil { + return fmt.Errorf("verifyTurnstile: %w", err) + } + reqs, err := http.NewRequestWithContext(ctx, "POST", "https://challenges.cloudflare.com/turnstile/v0/siteverify", bw) + if err != nil { + return fmt.Errorf("verifyTurnstile: %w", err) + } + reqs.Header.Set("Accept", "*/*") + reqs.Header.Set("Content-Type", "application/json") + rep, err := c.httpClient.Do(reqs) + if err != nil { + return fmt.Errorf("verifyTurnstile: %w", err) + } + defer rep.Body.Close() + + var t turnstileRet + err = json.NewDecoder(rep.Body).Decode(&t) + if err != nil { + return fmt.Errorf("verifyTurnstile: %w", err) + } + + if !t.Success { + return fmt.Errorf("verifyTurnstile: %w", errors.Join(ErrTurnstile{ + ErrorCodes: t.ErrorCodes, + }, ErrCaptcha)) + } + return nil +} + +type turnstileResponse struct { + Secret string `json:"secret"` + Response string `json:"response"` + Remoteip string `json:"remoteip"` +} + +type turnstileRet struct { + Success bool `json:"success"` + ErrorCodes []string `json:"error-codes"` +} + +var ErrCaptcha = errors.New("验证码错误") + +type ErrTurnstile struct { + ErrorCodes []string +} + +func (e ErrTurnstile) Error() string { + return strings.Join(e.ErrorCodes, " ") +} diff --git a/service/email/email.go b/service/email/email.go new file mode 100644 index 0000000..c1b0789 --- /dev/null +++ b/service/email/email.go @@ -0,0 +1,191 @@ +package email + +import ( + "bytes" + "context" + "crypto/rsa" + "errors" + "fmt" + "html/template" + "math/rand" + "net/url" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/samber/lo" + "github.com/wneessen/go-mail" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" +) + +type EmailConfig struct { + Host string + Port int + SSL bool + Name string + Pass string +} + +type EmailService struct { + emailConfig []EmailConfig + pri *rsa.PrivateKey + config config.Config + cache cache.Cache +} + +func NewEmail(pri *rsa.PrivateKey, c config.Config, cache cache.Cache) (*EmailService, error) { + ec := lo.Map[config.SmtpUser, EmailConfig](c.Email.Smtp, func(item config.SmtpUser, index int) EmailConfig { + return EmailConfig{ + Host: item.Host, + Port: item.Port, + SSL: item.SSL, + Name: item.Name, + Pass: item.Pass, + } + }) + + return &EmailService{ + emailConfig: ec, + pri: pri, + config: c, + cache: cache, + }, nil +} + +func (e EmailService) getRandEmailUser() (EmailConfig, error) { + if len(e.emailConfig) == 0 { + return EmailConfig{}, fmt.Errorf("没有可用的邮箱账号") + } + + i := rand.Intn(len(e.emailConfig)) + return e.emailConfig[i], nil +} + +func (e EmailService) SendEmail(ctx context.Context, to string, subject, body string) error { + u, err := e.getRandEmailUser() + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + m := mail.NewMsg() + + err = m.From(u.Name) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + + err = m.To(to) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + m.Subject(subject) + m.SetBodyString(mail.TypeTextHTML, body) + + c, err := mail.NewClient(u.Host, mail.WithPort(u.Port), mail.WithSMTPAuth(mail.SMTPAuthPlain), + mail.WithUsername(u.Name), mail.WithPassword(u.Pass)) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + if u.SSL { + c.SetSSL(true) + } + defer c.Close() + + err = c.DialAndSendWithContext(ctx, m) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + return nil +} + +var emailTemplate = lo.Must(template.New("email").Parse(`

{{ .msg }}

{{ .url }}`)) + +func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval int, host string, subject, msg, path string) error { + sendKey := []byte("SendEmail" + email) + sendB, err := e.cache.Get(sendKey) + if err != nil { + return fmt.Errorf("SendVerifyUrl: %w", err) + } + if sendB != nil { + return fmt.Errorf("SendVerifyUrl: %w", ErrSendLimit) + } + err = e.cache.Put(sendKey, []byte{1}, time.Now().Add(time.Second*time.Duration(interval))) + if err != nil { + return fmt.Errorf("SendVerifyUrl: %w", err) + } + + code, err := newJwtToken(e.pri, email, issuer+path) + if err != nil { + return fmt.Errorf("SendVerifyUrl: %w", err) + } + + q := url.Values{} + q.Set("code", code) + q.Set("email", email) + + u := url.URL{ + Host: host, + Scheme: "http", + Path: path, + } + u.RawQuery = q.Encode() + + if e.config.WebBaseUrl != "" { + webBase, err := url.Parse(e.config.WebBaseUrl) + if err != nil { + return fmt.Errorf("SendVerifyUrl: %w", err) + } + u.Host = webBase.Host + u.Scheme = webBase.Scheme + } + + body := bytes.NewBuffer(nil) + err = emailTemplate.Execute(body, map[string]any{ + "msg": msg, + "url": u.String(), + }) + if err != nil { + return fmt.Errorf("SendVerifyUrl: %w", err) + } + + err = e.SendEmail(ctx, email, subject, body.String()) + if err != nil { + return fmt.Errorf("SendVerifyUrl: %w", err) + } + return nil +} + +var ( + ErrSendLimit = errors.New("邮件发送限制") + ErrTokenInvalid = errors.New("token 无效") +) + +func (e EmailService) VerifyJwt(email, jwtStr, path string) error { + token, err := jwt.ParseWithClaims(jwtStr, &jwt.RegisteredClaims{}, func(t *jwt.Token) (interface{}, error) { + return &e.pri.PublicKey, nil + }) + if err != nil { + return fmt.Errorf("VerifyJwt: %w", err) + } + sub, _ := token.Claims.GetSubject() + iss, _ := token.Claims.GetIssuer() + if !token.Valid || sub != email || issuer+path != iss { + return fmt.Errorf("VerifyJwt: %w", ErrTokenInvalid) + } + return nil +} + +const issuer = "email" + +func newJwtToken(jwtKey *rsa.PrivateKey, email, iss string) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * 24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + Subject: email, + Issuer: iss, + }) + jwts, err := token.SignedString(jwtKey) + if err != nil { + return "", fmt.Errorf("newJwtToken: %w", err) + } + return jwts, nil +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..996c576 --- /dev/null +++ b/service/service.go @@ -0,0 +1,5 @@ +package service + +import "github.com/google/wire" + +var Service = wire.NewSet(NewUserSerice, NewTextureService, NewAdminService, NewWebService) diff --git a/service/texture.go b/service/texture.go new file mode 100644 index 0000000..07a23ac --- /dev/null +++ b/service/texture.go @@ -0,0 +1,112 @@ +package service + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "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" +) + +type TextureService struct { + client *ent.Client + config config.Config + cache cache.Cache +} + +func NewTextureService(client *ent.Client, config config.Config, cache cache.Cache) *TextureService { + return &TextureService{ + client: client, + config: config, + cache: cache, + } +} + +func (w *TextureService) 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/user.go b/service/user.go new file mode 100644 index 0000000..ab943f7 --- /dev/null +++ b/service/user.go @@ -0,0 +1,288 @@ +package service + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" + "github.com/xmdhs/authlib-skin/service/email" + "github.com/xmdhs/authlib-skin/utils" +) + +var ( + ErrExistUser = errors.New("邮箱已存在") + ErrExitsName = errors.New("用户名已存在") + ErrRegLimit = errors.New("超过注册 ip 限制") + ErrPassWord = errors.New("错误的密码或用户名") + ErrChangeName = errors.New("离线模式 uuid 不允许修改用户名") + ErrUsername = errors.New("邮箱不存在") +) + +type UserService struct { + config config.Config + client *ent.Client + captchaService *captcha.CaptchaService + authService *auth.AuthService + cache cache.Cache + emailService *email.EmailService +} + +func NewUserSerice(config config.Config, client *ent.Client, captchaService *captcha.CaptchaService, + authService *auth.AuthService, cache cache.Cache, emailService *email.EmailService) *UserService { + return &UserService{ + config: config, + client: client, + captchaService: captchaService, + authService: authService, + cache: cache, + emailService: emailService, + } +} + +func (w *UserService) Reg(ctx context.Context, u model.UserReg, ipPrefix, ip string) (model.LoginRep, error) { + var userUuid string + if w.config.OfflineUUID { + userUuid = utils.UUIDGen(u.Name) + } else { + userUuid = strings.ReplaceAll(uuid.New().String(), "-", "") + } + + if w.config.Email.Enable { + err := w.emailService.VerifyJwt(u.Email, u.EmailJwt, "/register") + if err != nil { + return model.LoginRep{}, fmt.Errorf("Reg: %w", err) + } + } + + err := w.captchaService.VerifyCaptcha(ctx, u.CaptchaToken, ip) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Reg: %w", err) + } + + if w.config.MaxIpUser != 0 { + c, err := w.client.User.Query().Where(user.RegIPEQ(ipPrefix)).Count(ctx) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Reg: %w", err) + } + if c >= w.config.MaxIpUser { + return model.LoginRep{}, fmt.Errorf("Reg: %w", ErrRegLimit) + } + } + + p, s := utils.Argon2ID(u.Password) + + var du *ent.User + + err = utils.WithTx(ctx, w.client, func(tx *ent.Tx) error { + count, err := tx.User.Query().Where(user.EmailEQ(u.Email)).ForUpdateA().Count(ctx) + if err != nil { + return err + } + if count != 0 { + return ErrExistUser + } + nameCount, err := tx.UserProfile.Query().Where(userprofile.NameEQ(u.Name)).ForUpdateA().Count(ctx) + if err != nil { + return err + } + if nameCount != 0 { + return ErrExitsName + } + du, err = tx.User.Create(). + SetEmail(u.Email). + SetPassword(p). + SetSalt(s). + SetRegTime(time.Now().Unix()). + SetRegIP(ipPrefix). + SetState(0).Save(ctx) + if err != nil { + return err + } + _, err = tx.UserProfile.Create(). + SetUser(du). + SetName(u.Name). + SetUUID(userUuid). + Save(ctx) + if err != nil { + return err + } + if du.ID == 1 { + err := tx.User.UpdateOne(du).SetState(auth.SetAdmin(0, true)).Exec(ctx) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Reg: %w", err) + } + jwt, err := w.authService.CreateToken(ctx, du, "web", userUuid) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Login: %w", err) + } + + return model.LoginRep{ + Token: jwt, + Name: u.Name, + UUID: userUuid, + }, nil +} + +func (w *UserService) Login(ctx context.Context, l model.Login, ip string) (model.LoginRep, error) { + err := w.captchaService.VerifyCaptcha(ctx, l.CaptchaToken, ip) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Login: %w", err) + } + u, err := w.client.User.Query().Where(user.Email(l.Email)).WithProfile().Only(ctx) + if err != nil { + var ne *ent.NotFoundError + if errors.As(err, &ne) { + return model.LoginRep{}, fmt.Errorf("Login: %w", ErrPassWord) + } + return model.LoginRep{}, fmt.Errorf("Login: %w", err) + } + err = validatePass(ctx, u, l.Password) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Login: %w", err) + } + jwt, err := w.authService.CreateToken(ctx, u, "web", u.Edges.Profile.UUID) + if err != nil { + return model.LoginRep{}, fmt.Errorf("Login: %w", err) + } + return model.LoginRep{ + Token: jwt, + Name: u.Edges.Profile.Name, + UUID: u.Edges.Profile.UUID, + }, nil +} + +func (w *UserService) Info(ctx context.Context, t *model.TokenClaims) (model.UserInfo, error) { + u, err := w.client.User.Query().Where(user.ID(t.UID)).First(ctx) + if err != nil { + return model.UserInfo{}, fmt.Errorf("Info: %w", err) + } + isAdmin := auth.IsAdmin(u.State) + return model.UserInfo{ + UID: t.UID, + UUID: t.Subject, + IsAdmin: isAdmin, + }, nil +} + +func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, uid int, validOldPass bool) error { + u, err := w.client.User.Query().Where(user.IDEQ(uid)).WithToken().First(ctx) + if err != nil { + return fmt.Errorf("ChangePasswd: %w", err) + } + if validOldPass { + err := validatePass(ctx, u, p.Old) + if err != nil { + return fmt.Errorf("ChangePasswd: %w", err) + } + } + pass, salt := utils.Argon2ID(p.New) + if u.Edges.Token != nil { + err := w.client.UserToken.UpdateOne(u.Edges.Token).AddTokenID(1).Exec(ctx) + if err != nil { + return fmt.Errorf("ChangePasswd: %w", err) + } + } + err = w.cache.Del([]byte("auth" + strconv.Itoa(uid))) + if err != nil { + return fmt.Errorf("ChangePasswd: %w", err) + } + err = w.client.User.UpdateOne(u).SetPassword(pass).SetSalt(salt).Exec(ctx) + if err != nil { + return fmt.Errorf("ChangePasswd: %w", err) + } + return nil +} + +func (w *UserService) changeName(ctx context.Context, newName string, uid int, uuid string) error { + if w.config.OfflineUUID { + return fmt.Errorf("changeName: %w", ErrChangeName) + } + c, err := w.client.UserProfile.Query().Where(userprofile.Name(newName)).Count(ctx) + if err != nil { + return fmt.Errorf("changeName: %w", err) + } + if c != 0 { + return fmt.Errorf("changeName: %w", ErrExitsName) + } + err = w.client.UserProfile.Update().Where(userprofile.HasUserWith(user.ID(uid))).SetName(newName).Exec(ctx) + if err != nil { + return fmt.Errorf("changeName: %w", err) + } + w.cache.Del([]byte("Profile" + uuid)) + return err +} + +func (w *UserService) ChangeName(ctx context.Context, newName string, t *model.TokenClaims) error { + err := w.changeName(ctx, newName, t.UID, t.Subject) + if err != nil { + return fmt.Errorf("ChangeName: %w", err) + } + return nil +} + +func (w *UserService) SendRegEmail(ctx context.Context, email, CaptchaToken, host, ip string) error { + err := w.captchaService.VerifyCaptcha(ctx, CaptchaToken, ip) + if err != nil { + return fmt.Errorf("SendRegEmail: %w", err) + } + + err = w.emailService.SendVerifyUrl(ctx, email, 60, host, "验证你的邮箱以完成注册", "点击下方链接完成注册,1 天内有效", "/register") + if err != nil { + return fmt.Errorf("SendRegEmail: %w", err) + } + return nil +} + +func (w *UserService) SendChangePasswordEmail(ctx context.Context, email, CaptchaToken, host, ip string) error { + err := w.captchaService.VerifyCaptcha(ctx, CaptchaToken, ip) + if err != nil { + return fmt.Errorf("SendChangePasswordEmail: %w", err) + } + c, err := w.client.User.Query().Where(user.Email(email)).Count(ctx) + if err != nil { + return fmt.Errorf("SendChangePasswordEmail: %w", err) + } + if c == 0 { + return fmt.Errorf("SendChangePasswordEmail: %w", ErrUsername) + } + err = w.emailService.SendVerifyUrl(ctx, email, 60, host, "重设密码", "点击下方链接更改你的密码,1 天内有效", "/forgot") + if err != nil { + return fmt.Errorf("SendChangePasswordEmail: %w", err) + } + return nil +} + +func (w *UserService) ForgotPassword(ctx context.Context, email, passWord, emailJwt string) error { + err := w.emailService.VerifyJwt(email, emailJwt, "/forgot") + if err != nil { + return fmt.Errorf("ForgotPassword: %w", err) + } + u, err := w.client.User.Query().Where(user.Email(email)).First(ctx) + if err != nil { + return fmt.Errorf("ForgotPassword: %w", err) + } + + err = w.ChangePasswd(ctx, model.ChangePasswd{New: passWord}, u.ID, false) + if err != nil { + return fmt.Errorf("ForgotPassword: %w", err) + } + return nil +} diff --git a/service/user_test.go b/service/user_test.go new file mode 100644 index 0000000..73a5878 --- /dev/null +++ b/service/user_test.go @@ -0,0 +1,164 @@ +package service + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "net/http" + "os" + "testing" + + _ "github.com/go-sql-driver/mysql" + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/migrate" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" + "github.com/xmdhs/authlib-skin/service/email" +) + +var ( + userSerice *UserService + adminSerice *AdminService +) + +func TestMain(m *testing.M) { + ctx := context.Background() + + clean := initSerice(ctx) + code := m.Run() + + clean() + + os.Exit(code) +} + +func initSerice(ctx context.Context) func() { + c := lo.Must(ent.Open("mysql", "root:root@tcp(127.0.0.1)/test")) + lo.Must0(c.Schema.Create(context.Background(), migrate.WithForeignKeys(false), migrate.WithDropIndex(true), migrate.WithDropColumn(true))) + rsa4 := lo.Must(rsa.GenerateKey(rand.Reader, 4096)) + cache := cache.NewFastCache(100000) + config := config.Default() + authService := auth.NewAuthService(c, cache, &rsa4.PublicKey, rsa4) + email := lo.Must(email.NewEmail(rsa4, config, cache)) + + userSerice = NewUserSerice(config, c, captcha.NewCaptchaService(config, &http.Client{}), authService, cache, email) + adminSerice = NewAdminService(authService, c, config, cache) + + return func() { + c.User.Delete().Exec(ctx) + c.Texture.Delete().Exec(ctx) + c.UserProfile.Delete().Exec(ctx) + c.UserTexture.Delete().Exec(ctx) + c.UserToken.Delete().Exec(ctx) + } +} + +func TestUserSerice_Reg(t *testing.T) { + ctx := context.Background() + userSerice.config.MaxIpUser = 1 + type args struct { + ctx context.Context + u model.UserReg + ipPrefix string + ip string + } + tests := []struct { + name string + w *UserService + args args + wantErr bool + }{ + { + name: "1", + w: userSerice, + args: args{ + ctx: ctx, + u: model.UserReg{ + Email: "1@xmdhs.com", + Password: "123456", + Name: "111", + CaptchaToken: "", + }, + ipPrefix: "127.0.0.0/24", + ip: "127.0.0.1", + }, + wantErr: false, + }, + { + name: "email duplicate", + w: userSerice, + args: args{ + ctx: ctx, + u: model.UserReg{ + Email: "1@xmdhs.com", + Password: "123456", + Name: "111", + CaptchaToken: "", + }, + ipPrefix: "127.0.0.0/24", + ip: "127.0.0.1", + }, + wantErr: true, + }, + { + name: "name duplicate", + w: userSerice, + args: args{ + ctx: ctx, + u: model.UserReg{ + Email: "2@xmdhs.com", + Password: "123456", + Name: "111", + CaptchaToken: "", + }, + ipPrefix: "127.0.0.0/24", + ip: "127.0.0.1", + }, + wantErr: true, + }, + { + name: "MaxIpUser", + w: userSerice, + args: args{ + ctx: ctx, + u: model.UserReg{ + Email: "3@xmdhs.com", + Password: "123456", + Name: "333", + CaptchaToken: "", + }, + ipPrefix: "127.0.0.0/24", + ip: "127.0.0.1", + }, + wantErr: true, + }, + { + name: "MaxIpUser", + w: userSerice, + args: args{ + ctx: ctx, + u: model.UserReg{ + Email: "4@xmdhs.com", + Password: "123456", + Name: "444", + CaptchaToken: "", + }, + ipPrefix: "127.0.0.2/24", + ip: "127.0.0.1", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _, err := tt.w.Reg(tt.args.ctx, tt.args.u, tt.args.ipPrefix, tt.args.ip); (err != nil) != tt.wantErr { + t.Errorf("WebService.Reg() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } + userSerice.config.MaxIpUser = 0 +} diff --git a/service/utils/texture.go b/service/utils/texture.go new file mode 100644 index 0000000..6b63af2 --- /dev/null +++ b/service/utils/texture.go @@ -0,0 +1,68 @@ +package utils + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/samber/lo" + "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, texturePath string) 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(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 +} diff --git a/service/web.go b/service/web.go new file mode 100644 index 0000000..0e81a88 --- /dev/null +++ b/service/web.go @@ -0,0 +1,43 @@ +package service + +import ( + "context" + "fmt" + + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/utils" +) + +type WebService struct { + config config.Config +} + +func NewWebService(c config.Config) *WebService { + return &WebService{ + config: c, + } +} + +func validatePass(ctx context.Context, u *ent.User, password string) error { + if !utils.Argon2Compare(password, u.Password, u.Salt) { + return fmt.Errorf("validatePass: %w", ErrPassWord) + } + return nil +} + +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, + }, + ServerName: w.config.ServerName, + AllowChangeName: !w.config.OfflineUUID, + NeedEmail: w.config.Email.Enable, + AllowDomain: w.config.Email.AllowDomain, + EmailReg: w.config.Email.EmailReg, + EmailRegMsg: w.config.Email.EmailRegMsg, + } +} diff --git a/service/yggdrasil/session.go b/service/yggdrasil/session.go new file mode 100644 index 0000000..821fe30 --- /dev/null +++ b/service/yggdrasil/session.go @@ -0,0 +1,57 @@ +package yggdrasil + +import ( + "context" + "fmt" + "time" + + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/service/auth" +) + +type sessionWithIP struct { + User model.TokenClaims + IP string +} + +func (y *Yggdrasil) SessionJoin(ctx context.Context, s yggdrasil.Session, t *model.TokenClaims, ip string) error { + if s.SelectedProfile != t.Subject { + return fmt.Errorf("SessionJoin: %w", auth.ErrTokenInvalid) + } + err := cache.CacheHelp[sessionWithIP]{Cache: y.cache}.Put([]byte("session"+s.ServerID), sessionWithIP{ + User: *t, + IP: ip, + }, time.Now().Add(30*time.Second)) + if err != nil { + return fmt.Errorf("SessionJoin: %w", err) + } + return nil +} + +func (y *Yggdrasil) HasJoined(ctx context.Context, username, serverId string, ip string, host string) (yggdrasil.UserInfo, error) { + sIP, err := cache.CacheHelp[sessionWithIP]{Cache: y.cache}.Get([]byte("session" + serverId)) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("HasJoined: %w", err) + } + if ip != "" && ip != sIP.IP { + return yggdrasil.UserInfo{}, fmt.Errorf("ip 不相同") + } + + up, err := y.client.UserProfile.Query().Where(userprofile.Name(username)).Only(ctx) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("HasJoined: %w", err) + } + + if up.UUID != sIP.User.Subject { + return yggdrasil.UserInfo{}, fmt.Errorf("uuid 不相同") + } + + u, err := y.GetProfile(ctx, up.UUID, false, host) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("HasJoined: %w", err) + } + return u, nil +} diff --git a/service/yggdrasil/texture.go b/service/yggdrasil/texture.go new file mode 100644 index 0000000..503a61a --- /dev/null +++ b/service/yggdrasil/texture.go @@ -0,0 +1,36 @@ +package yggdrasil + +import ( + "context" + "errors" + "fmt" + + "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" +) + +var ( + ErrUUIDNotEq = errors.New("uuid 不相同") +) + +func (y *Yggdrasil) delTexture(ctx context.Context, userProfileID int, textureType string) error { + return utilsService.DelTexture(ctx, userProfileID, textureType, y.client, y.config.TexturePath) +} + +func (y *Yggdrasil) DelTexture(ctx context.Context, t *model.TokenClaims, textureType string) error { + up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx) + if err != nil { + return fmt.Errorf("DelTexture: %w", err) + } + err = y.delTexture(ctx, up.ID, textureType) + if err != nil { + return fmt.Errorf("DelTexture: %w", err) + } + err = y.cache.Del([]byte("Profile" + t.Subject)) + if err != nil { + return fmt.Errorf("DelTexture: %w", err) + } + return nil +} diff --git a/service/yggdrasil/user.go b/service/yggdrasil/user.go new file mode 100644 index 0000000..95a4899 --- /dev/null +++ b/service/yggdrasil/user.go @@ -0,0 +1,318 @@ +package yggdrasil + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/binary" + "errors" + "fmt" + "math/big" + "net/url" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/db/cache" + "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/db/ent/usertoken" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/utils" + "github.com/xmdhs/authlib-skin/utils/sign" +) + +var ( + ErrRate = errors.New("频率限制") + ErrPassWord = errors.New("错误的密码或邮箱") + ErrNotUser = errors.New("没有这个用户") + ErrUserDisable = errors.New("用户被禁用") +) + +func (y *Yggdrasil) validatePass(cxt context.Context, email, pass string) (*ent.User, error) { + err := rate("validatePass"+email, y.cache, 10*time.Second, 3) + if err != nil { + return nil, fmt.Errorf("validatePass: %w", err) + } + u, err := y.client.User.Query().Where(user.EmailEQ(email)).WithProfile().First(cxt) + if err != nil { + var nf *ent.NotFoundError + if errors.As(err, &nf) { + return nil, fmt.Errorf("validatePass: %w", errors.Join(ErrPassWord, err)) + } + return nil, fmt.Errorf("validatePass: %w", err) + } + if !utils.Argon2Compare(pass, u.Password, u.Salt) { + return nil, fmt.Errorf("validatePass: %w", ErrPassWord) + } + return u, nil +} + +func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticate) (yggdrasil.Token, error) { + u, err := y.validatePass(cxt, auth.Username, auth.Password) + if err != nil { + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) + } + + clientToken := auth.ClientToken + if clientToken == "" { + clientToken = strings.ReplaceAll(uuid.New().String(), "-", "") + } + + jwts, err := y.authService.CreateToken(cxt, u, clientToken, u.Edges.Profile.UUID) + if err != nil { + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) + } + + if u.Edges.Profile == nil { + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", ErrUserDisable) + } + p := yggdrasil.UserInfo{ + ID: u.Edges.Profile.UUID, + Name: u.Edges.Profile.Name, + } + return yggdrasil.Token{ + AccessToken: jwts, + AvailableProfiles: []yggdrasil.UserInfo{p}, + ClientToken: clientToken, + SelectedProfile: p, + User: yggdrasil.TokenUserID{ + ID: utils.UUIDGen(strconv.Itoa(u.ID)), + Properties: []any{}, + }, + }, nil +} + +func (y *Yggdrasil) SignOut(ctx context.Context, t yggdrasil.Pass) error { + u, err := y.validatePass(ctx, t.Username, t.Password) + if err != nil { + return fmt.Errorf("SignOut: %w", err) + } + ut, err := y.client.UserToken.Query().Where(usertoken.HasUserWith(user.IDEQ(u.ID))).First(ctx) + if err != nil { + var nf *ent.NotFoundError + if !errors.As(err, &nf) { + return fmt.Errorf("SignOut: %w", err) + } + return nil + } + err = y.client.UserToken.UpdateOne(ut).AddTokenID(1).Exec(ctx) + if err != nil { + return fmt.Errorf("SignOut: %w", err) + } + err = y.cache.Del([]byte("auth" + strconv.Itoa(u.ID))) + if err != nil { + return fmt.Errorf("SignOut: %w", err) + } + return nil +} + +func (y *Yggdrasil) Invalidate(ctx context.Context, t *model.TokenClaims) error { + err := y.client.UserToken.Update().Where(usertoken.HasUserWith(user.ID(t.UID))).AddTokenID(1).Exec(ctx) + if err != nil { + return fmt.Errorf("Invalidate: %w", err) + } + err = y.cache.Del([]byte("auth" + strconv.Itoa(t.UID))) + if err != nil { + return fmt.Errorf("Invalidate: %w", err) + } + return nil +} + +func (y *Yggdrasil) Refresh(ctx context.Context, t *model.TokenClaims) (yggdrasil.Token, error) { + jwts, err := newJwtToken(y.prikey, t.Tid, t.CID, t.Subject, t.UID) + if err != nil { + return yggdrasil.Token{}, fmt.Errorf("Refresh: %w", err) + } + + up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx) + if err != nil { + return yggdrasil.Token{}, fmt.Errorf("Refresh: %w", err) + } + u := yggdrasil.UserInfo{ID: up.UUID, Name: up.Name} + + return yggdrasil.Token{ + AccessToken: jwts, + AvailableProfiles: []yggdrasil.UserInfo{u}, + ClientToken: t.CID, + SelectedProfile: u, + User: yggdrasil.TokenUserID{ + ID: utils.UUIDGen(strconv.Itoa(t.UID)), + Properties: []any{}, + }, + }, nil +} + +func (y *Yggdrasil) GetProfile(ctx context.Context, uuid string, unsigned bool, host string) (yggdrasil.UserInfo, error) { + c := cache.CacheHelp[yggdrasil.UserTextures]{Cache: y.cache} + key := []byte("Profile" + uuid) + ut, err := c.Get(key) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("GetProfile: %w", err) + } + if ut.ProfileName == "" { + up, err := y.client.UserProfile.Query().Where(userprofile.UUID(uuid)).WithUsertexture().Only(ctx) + if err != nil { + var nf *ent.NotFoundError + if errors.As(err, &nf) { + return yggdrasil.UserInfo{}, fmt.Errorf("GetProfile: %w", ErrNotUser) + } + return yggdrasil.UserInfo{}, fmt.Errorf("GetProfile: %w", err) + } + + ut = yggdrasil.UserTextures{ + ProfileID: up.UUID, + ProfileName: up.Name, + Textures: map[string]yggdrasil.Textures{}, + Timestamp: time.Now().UnixMilli(), + } + + baseURl := func() string { + if y.config.TextureBaseUrl == "" { + u := &url.URL{} + u.Host = host + u.Scheme = "http" + u.Path = "texture" + return u.String() + } + return y.config.TextureBaseUrl + }() + + for _, v := range up.Edges.Usertexture { + dt, err := y.client.Texture.Query().Where(texture.ID(v.TextureID)).Only(ctx) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("GetProfile: %w", err) + } + hashstr := dt.TextureHash + t := yggdrasil.Textures{ + Url: lo.Must1(url.JoinPath(baseURl, hashstr[:2], hashstr[2:4], hashstr)), + Metadata: map[string]string{}, + } + if v.Variant == "slim" { + t.Metadata["model"] = "slim" + } + ut.Textures[strings.ToTitle(v.Type)] = t + } + err = c.Put(key, ut, time.Now().Add(30*time.Minute)) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("GetProfile: %w", err) + } + } + + texturesBase64 := ut.Base64() + + pl := []yggdrasil.UserProperties{{ + Name: "textures", + Value: texturesBase64, + }} + + if !unsigned { + s := sign.NewAuthlibSignWithKey(y.prikey) + for i, v := range pl { + sign, err := s.Sign([]byte(v.Value)) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("GetProfile: %w", err) + } + pl[i].Signature = sign + } + } + + uinfo := yggdrasil.UserInfo{ + ID: ut.ProfileID, + Name: ut.ProfileName, + Properties: pl, + } + + return uinfo, nil +} + +func (y *Yggdrasil) BatchProfile(ctx context.Context, names []string) ([]yggdrasil.UserInfo, error) { + pl, err := y.client.UserProfile.Query().Where(userprofile.NameIn(names...)).All(ctx) + if err != nil { + return nil, fmt.Errorf("BatchProfile: %w", err) + } + return lo.Map[*ent.UserProfile, yggdrasil.UserInfo](pl, func(item *ent.UserProfile, index int) yggdrasil.UserInfo { + return yggdrasil.UserInfo{ + ID: item.UUID, + Name: item.Name, + } + }), nil +} + +// publicKey 为 PKIX,但要求 pem type 为 RSA PUBLIC KEY +// privateKey 为 PKCS #8, pem type 为 RSA PUBLIC KEY +// 签名使用 rsaWIthsha1 + +func (y *Yggdrasil) PlayerCertificates(ctx context.Context, t *model.TokenClaims) (yggdrasil.Certificates, error) { + rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err) + } + + s := sign.NewAuthlibSignWithKey(rsa2048) + priKey := lo.Must(s.GetPriKey()) + pubKey := lo.Must(s.GetPKIXPubKey()) + + expiresAt := time.Now().Add(24 * time.Hour) + expiresAtUnix := expiresAt.UnixMilli() + + pubV2, err := publicKeySignatureV2(&rsa2048.PublicKey, t.Subject, expiresAtUnix) + if err != nil { + return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err) + } + pub := publicKeySignature(pubKey, expiresAtUnix) + + servicePri := sign.NewAuthlibSignWithKey(y.prikey) + + pubV2Base64, err := servicePri.Sign(pubV2) + if err != nil { + return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err) + } + pubBase64, err := servicePri.Sign(pub) + if err != nil { + return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err) + } + + return yggdrasil.Certificates{ + ExpiresAt: expiresAt.Format(time.RFC3339Nano), + KeyPair: yggdrasil.CertificatesKeyPair{ + PrivateKey: priKey, + PublicKey: pubKey, + }, + PublicKeySignature: pubBase64, + PublicKeySignatureV2: pubV2Base64, + RefreshedAfter: time.Now().Format(time.RFC3339Nano), + }, nil + +} + +func publicKeySignatureV2(key *rsa.PublicKey, uuid string, expiresAt int64) ([]byte, error) { + bf := &bytes.Buffer{} + u := big.Int{} + u.SetString(uuid, 16) + bf.Write(u.Bytes()) + + eb := make([]byte, 8) + binary.BigEndian.PutUint64(eb, uint64(expiresAt)) + bf.Write(eb) + pubKey, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return nil, fmt.Errorf("publicKeySignatureV2: %w", err) + } + bf.Write(pubKey) + return bf.Bytes(), nil +} + +func publicKeySignature(key string, expiresAt int64) []byte { + bf := &bytes.Buffer{} + bf.WriteString(strconv.FormatInt(expiresAt, 10)) + bf.WriteString(key) + return bf.Bytes() +} diff --git a/service/yggdrasil/user_test.go b/service/yggdrasil/user_test.go new file mode 100644 index 0000000..c8ab0be --- /dev/null +++ b/service/yggdrasil/user_test.go @@ -0,0 +1,62 @@ +package yggdrasil + +import ( + "crypto" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "os" + "testing" + "time" + + "github.com/samber/lo" +) + +const ( + publicKeySignatureV2Str = "sB+BdmutEFqkIoIDOSPOrle8tCQl+lv8pCKqviivreZ0Et8zNgUtFagR9jvWiGfhSbOtQXf1ooDZDQx/TO58KA9k1gSoKoB3IWvxUooW5fiAftH+VvFluO+c9bulWiZ/Lk28EOk/moaGtovgYzMtt6F4BIJYB/iZLSkuRp/MiJqsUJcDN5DWViizWSjBsuAkiV+6+/T1QW9Z6IKrOvlLfJqhm/HSXgTuxS4CbzzqxGWZL49TDdeZFs0q1O+NgK2k5zLeL1JjfFdS6hu5X13pa9ygmTLrFIwuml9rT65mlQV5KSXGkO7uajKVpKBfqoM8gZ42wc20kyItDiSVIw4afoEpdr5AtlOL4VIK+Qnphty3YS18sbZzIukIMxMN/s/i90QA9xuMy/U0a8RwOq0haFJ5OYES9EFIYJjqa7uZJ1+riSJvXWQZwbh4l09lQ5p4/TBCDIqa3cp+YH2kVM8JCxhsLNmkkoXlV9LSJQ2zyAe1ItkrIi/HYx2qLQ56CIel5qM4j60+kPpoGfmvgeEiHDERloH9lekHHUeRXawXV6RE1wwf41Qi9+3UycJQWGOi03qprE42YjsRMi3BUToEQqK1QOPJo8x2F/YfGVRJpLMBOS1g25uqw/kIOBAzMWfGRtQ5iKuPzFGhLx+rJjp58pyr0939KMvQtq/71ADQllM=" + publicKeySignatureStr = "kxjk7pdzEyQ5Pj+OOza2hmMFFZ1RHSqAHv1DcHaF+um5ZLRBvuwRTmbmvwuq4IFafdPvwaGqohNF7U9EXv+/v2JrEkZQWRXTfhzx+WSpogfDFtIOe20QFKOfZ1kBgPYaqCjSx67idvgaCMxafjWZDmZEMfJLv5TL1rO2LHnyeMIlFdVRhVbRiwa8f7RSPcNhLL2I0zHl40EG7lryIW08bctd4Ksgd0eDYw/tJCfY79NV1PurKE+9YCr2fK1ErhSfirkdfmXptEQ9iEZ+YRwkgYZRI6bQiwwpMqMDFLNOL5STxuEZz6ynygRI2WL+n09FYt066Eci3RC9lRVt3VUrRO2TWx3stwzI8zktrIkplYUC1l/ECtt+RBgN4Fc7QU4RX1ZBirUPDykK2gObS5OtowTi0o3tTPOnl0Jy/a1vr+Nqc1TPlDomE6xOBDtCD61sMuPedNM8IoBl71OphR91t4oxDDe5mZMZBCc27J6apjy3rfzL4Xpi9g72047QKYrQEYNWpF9ddkX/Ed8roC/e7Zaa77ARhSDfBbKs9gRvILqFZ8eprMBGzn55K4QI23l8G1X7IS4squaWkRNi+LXRq8M936DqYRIZ9bloJa5W+XJPGn29dWGxPlto2ELzGLI/69vemqNiKLmoXocBRByVAi7TU/Tqr9ZiJU1RBjsdh2w=" + publikKey = "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvUP6vXby++vuXGONEve5grn9cu2Zv3KQ\nXimz2kdIlN8DZ3u4ZUHkS6b1vWpOhS/IWE5TIp2iA7HOVH6WOTK3uViBhMXaMZ5eFuqASfA/jPF8\nxaGmHiH4dBmGzbO7B+DRvL38PYLCTMzgwxp1qEuW7VVmcd6Glr0HG9q3YDB0rb9gjPhvj6seT6sf\ntoYC0wHX29qUe4Y89bgSsF9WDf0lQ+qdjLwlcP/Aqyc+VaCYLV67LiRDjQc8q2JmPoy4KEwxOYuD\nEfHSfHzu48xn2mfvVMJXRU09VDLvvXcteEt7KLF6a1QizNx51AXwUq8xre42dgQkPF4Dnv4JxysD\niMVHzwIDAQAB\n-----END RSA PUBLIC KEY-----\n" + expiresAt = "2023-09-17T03:06:05.251232611Z" +) + +func Test_publicKeySignature(t *testing.T) { + b, err := os.ReadFile("yggdrasil_session_pubkey.der") + if err != nil { + t.Fatal(err) + } + pub := lo.Must(x509.ParsePKIXPublicKey(b)).(*rsa.PublicKey) + pubByte, _ := pem.Decode([]byte(publikKey)) + if pubByte == nil { + t.FailNow() + } + + userPub := lo.Must(x509.ParsePKIXPublicKey(pubByte.Bytes)).(*rsa.PublicKey) + timeUinx := lo.Must(time.Parse(time.RFC3339Nano, expiresAt)).UnixMilli() + + t.Run("publicKeySignatureV2", func(t *testing.T) { + signByte, err := publicKeySignatureV2(userPub, "9f51573a5ec545828c2b09f7f08497b1", timeUinx) + if err != nil { + t.Fatal(err) + } + + hashed := sha1.Sum(signByte) + + err = rsa.VerifyPKCS1v15(pub, crypto.SHA1, hashed[:], lo.Must(base64.StdEncoding.DecodeString(publicKeySignatureV2Str))) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("publicKeySignature", func(t *testing.T) { + b := publicKeySignature(publikKey, timeUinx) + + hashed := sha1.Sum(b) + + err = rsa.VerifyPKCS1v15(pub, crypto.SHA1, hashed[:], lo.Must(base64.StdEncoding.DecodeString(publicKeySignatureStr))) + if err != nil { + t.Fatal(err) + } + }) +} diff --git a/service/yggdrasil/yggdrasil.go b/service/yggdrasil/yggdrasil.go new file mode 100644 index 0000000..b0ac327 --- /dev/null +++ b/service/yggdrasil/yggdrasil.go @@ -0,0 +1,111 @@ +package yggdrasil + +import ( + "context" + "crypto/rsa" + "crypto/x509" + _ "embed" + "encoding/base64" + "encoding/binary" + "fmt" + "sync" + "time" + + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/service/auth" +) + +type Yggdrasil struct { + client *ent.Client + cache cache.Cache + config config.Config + prikey *rsa.PrivateKey + authService *auth.AuthService + pubStr func() string +} + +func NewYggdrasil(client *ent.Client, cache cache.Cache, c config.Config, prikey *rsa.PrivateKey, authService *auth.AuthService) *Yggdrasil { + return &Yggdrasil{ + client: client, + cache: cache, + config: c, + prikey: prikey, + pubStr: sync.OnceValue[string](func() string { + derBytes := lo.Must(x509.MarshalPKIXPublicKey(&prikey.PublicKey)) + return base64.StdEncoding.EncodeToString(derBytes) + }), + authService: authService, + } +} + +func rate(k string, c cache.Cache, d time.Duration, count uint) error { + key := []byte(k) + v, err := c.Get([]byte(key)) + if err != nil { + return fmt.Errorf("rate: %w", err) + } + if v == nil { + err := putUint(1, c, key, d) + if err != nil { + return fmt.Errorf("rate: %w", err) + } + return nil + } + n := binary.BigEndian.Uint64(v) + if n > uint64(count) { + return fmt.Errorf("rate: %w", ErrRate) + } + err = putUint(n+1, c, key, d) + if err != nil { + return fmt.Errorf("rate: %w", err) + } + return nil +} + +func putUint(n uint64, c cache.Cache, key []byte, d time.Duration) error { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, n) + err := c.Put(key, b, time.Now().Add(d)) + if err != nil { + return fmt.Errorf("rate: %w", err) + } + return nil +} + +func newJwtToken(jwtKey *rsa.PrivateKey, tokenID, clientToken, UUID string, userID int) (string, error) { + return auth.NewJwtToken(jwtKey, tokenID, clientToken, UUID, userID) +} + +func (y *Yggdrasil) Auth(ctx context.Context, t yggdrasil.ValidateToken) (*model.TokenClaims, error) { + u, err := y.authService.Auth(ctx, t, true) + if err != nil { + return nil, fmt.Errorf("ValidateToken: %w", err) + } + return u, nil +} + +//go:embed yggdrasil_session_pubkey.der +var mojangPubKey []byte + +var mojangPubKeyStr = sync.OnceValue(func() string { + pub := lo.Must(x509.ParsePKIXPublicKey(mojangPubKey)).(*rsa.PublicKey) + derBytes := lo.Must(x509.MarshalPKIXPublicKey(pub)) + return base64.StdEncoding.EncodeToString(derBytes) +}) + +func (y *Yggdrasil) PublicKeys(ctx context.Context) yggdrasil.PublicKeys { + mojangPub := mojangPubKeyStr() + myPub := y.pubStr() + + pl := []yggdrasil.PublicKeyList{{PublicKey: mojangPub}, {PublicKey: myPub}} + + return yggdrasil.PublicKeys{ + PlayerCertificateKeys: pl, + ProfilePropertyKeys: pl, + } +} diff --git a/service/yggdrasil/yggdrasil_session_pubkey.der b/service/yggdrasil/yggdrasil_session_pubkey.der new file mode 100644 index 0000000000000000000000000000000000000000..9c79a3aa4771da1f15af37a2af0898f878ad816f GIT binary patch literal 550 zcmV+>0@?jAf&wBi4F(A+hDe6@4FLfG1potr0uKN%f&vNxf&u{m%20R*skxUv>`)yD7%qARQ>bP36CtP}x@~Z9*VkPYUS6pWrZtT#q)8GCbf0*5+4Pyw;#Tq!Aq{bDO|Iw<43LnG*#4;?Z&LM71zXE@J`@_dMl`?qQ1 z@3qR;mm=XG_-{G#4vhRxmZiQslrTtB_&7&0ECnD9)gZ}j`e&Je+s