From be5362913b70abc2e99ea13b16f6e305cedddd8b Mon Sep 17 00:00:00 2001 From: yvan Date: Thu, 13 Aug 2026 18:22:43 +0800 Subject: [PATCH] feat: initial commit - kratos admin service --- .codegraph/.gitignore | 5 + .gitattributes | 23 + .gitignore | 40 + AGENTS.md | 142 +++ CLAUDE.md | 142 +++ Dockerfile | 24 + LICENSE | 21 + Makefile | 56 + README.md | 67 + api/kratos/admin/v1/admin.pb.go | 710 +++++++++++ api/kratos/admin/v1/admin.proto | 161 +++ api/kratos/admin/v1/admin_grpc.pb.go | 408 +++++++ api/kratos/admin/v1/admin_http.pb.go | 390 ++++++ api/kratos/admin/v1/error_reason.pb.go | 153 +++ api/kratos/admin/v1/error_reason.proto | 27 + buf.gen.config.yaml | 7 + buf.gen.yaml | 21 + buf.lock | 6 + buf.yaml | 6 + configs/config.yaml | 15 + go.mod | 55 + go.sum | 139 +++ gva | 1 + internal/biz/README.md | 1 + internal/biz/admin.go | 136 +++ internal/biz/admin_test.go | 166 +++ internal/biz/biz.go | 6 + internal/biz/errors.go | 14 + internal/biz/pagination.go | 39 + internal/conf/conf.pb.go | 516 ++++++++ internal/conf/conf.proto | 41 + internal/data/README.md | 1 + internal/data/admin.go | 127 ++ internal/data/data.go | 43 + internal/data/ent/admin.go | 172 +++ internal/data/ent/admin/admin.go | 116 ++ internal/data/ent/admin/where.go | 510 ++++++++ internal/data/ent/admin_create.go | 859 +++++++++++++ internal/data/ent/admin_delete.go | 88 ++ internal/data/ent/admin_query.go | 527 ++++++++ internal/data/ent/admin_update.go | 382 ++++++ internal/data/ent/client.go | 366 ++++++ internal/data/ent/ent.go | 608 +++++++++ internal/data/ent/enttest/enttest.go | 84 ++ internal/data/ent/generate.go | 3 + internal/data/ent/hook/hook.go | 199 +++ internal/data/ent/migrate/migrate.go | 64 + internal/data/ent/migrate/schema.go | 35 + internal/data/ent/mutation.go | 684 +++++++++++ internal/data/ent/predicate/predicate.go | 10 + internal/data/ent/runtime.go | 48 + internal/data/ent/runtime/runtime.go | 10 + internal/data/ent/schema/admin.go | 27 + internal/data/ent/tx.go | 236 ++++ internal/server/grpc.go | 33 + internal/server/http.go | 37 + internal/server/server.go | 8 + internal/service/README.md | 1 + internal/service/admin.go | 232 ++++ internal/service/service.go | 6 + openapi.yaml | 249 ++++ pkg/auth/auth.go | 32 + pkg/auth/config.go | 22 + pkg/auth/middleware.go | 100 ++ pkg/auth/middleware_test.go | 125 ++ pkg/auth/token.go | 48 + pkg/validate/validate.go | 24 + web/.commitlintrc.js | 1 + web/.editorconfig | 16 + web/.gitignore | 41 + web/.husky/commit-msg | 1 + web/.husky/pre-commit | 1 + web/.lintstagedrc | 5 + web/.npmrc | 1 + web/LICENSE | 21 + web/biome.json | 49 + web/config/config.ts | 157 +++ web/config/defaultSettings.ts | 28 + web/config/proxy.ts | 23 + web/config/routes.ts | 47 + web/jest.config.ts | 21 + web/package.json | 96 ++ web/public/CNAME | 1 + web/public/favicon.ico | Bin 0 -> 4286 bytes web/public/icons/icon-128x128.png | Bin 0 -> 1329 bytes web/public/icons/icon-192x192.png | Bin 0 -> 1856 bytes web/public/icons/icon-512x512.png | Bin 0 -> 5082 bytes web/public/logo.svg | 1 + web/public/pro_icon.svg | 5 + web/public/scripts/loading.js | 202 +++ web/src/access.ts | 13 + web/src/app.tsx | 143 +++ web/src/components/Footer/index.tsx | 36 + web/src/components/HeaderDropdown/index.tsx | 41 + .../RightContent/AvatarDropdown.tsx | 142 +++ web/src/components/RightContent/index.tsx | 32 + web/src/components/index.ts | 12 + web/src/global.less | 94 ++ web/src/global.style.ts | 42 + web/src/global.tsx | 93 ++ web/src/loading.tsx | 7 + web/src/locales/bn-BD.ts | 25 + web/src/locales/bn-BD/component.ts | 5 + web/src/locales/bn-BD/globalHeader.ts | 17 + web/src/locales/bn-BD/menu.ts | 52 + web/src/locales/bn-BD/pages.ts | 76 ++ web/src/locales/bn-BD/pwa.ts | 7 + web/src/locales/bn-BD/settingDrawer.ts | 32 + web/src/locales/bn-BD/settings.ts | 60 + web/src/locales/en-US.ts | 25 + web/src/locales/en-US/component.ts | 5 + web/src/locales/en-US/globalHeader.ts | 18 + web/src/locales/en-US/menu.ts | 52 + web/src/locales/en-US/pages.ts | 79 ++ web/src/locales/en-US/pwa.ts | 7 + web/src/locales/en-US/settingDrawer.ts | 32 + web/src/locales/en-US/settings.ts | 61 + web/src/locales/fa-IR.ts | 25 + web/src/locales/fa-IR/component.ts | 5 + web/src/locales/fa-IR/globalHeader.ts | 18 + web/src/locales/fa-IR/menu.ts | 52 + web/src/locales/fa-IR/pages.ts | 77 ++ web/src/locales/fa-IR/pwa.ts | 7 + web/src/locales/fa-IR/settingDrawer.ts | 32 + web/src/locales/fa-IR/settings.ts | 64 + web/src/locales/id-ID.ts | 25 + web/src/locales/id-ID/component.ts | 5 + web/src/locales/id-ID/globalHeader.ts | 18 + web/src/locales/id-ID/menu.ts | 53 + web/src/locales/id-ID/pages.ts | 78 ++ web/src/locales/id-ID/pwa.ts | 7 + web/src/locales/id-ID/settingDrawer.ts | 33 + web/src/locales/id-ID/settings.ts | 64 + web/src/locales/ja-JP.ts | 25 + web/src/locales/ja-JP/component.ts | 5 + web/src/locales/ja-JP/globalHeader.ts | 17 + web/src/locales/ja-JP/menu.ts | 52 + web/src/locales/ja-JP/pages.ts | 80 ++ web/src/locales/ja-JP/pwa.ts | 7 + web/src/locales/ja-JP/settingDrawer.ts | 33 + web/src/locales/ja-JP/settings.ts | 63 + web/src/locales/pt-BR.ts | 22 + web/src/locales/pt-BR/component.ts | 5 + web/src/locales/pt-BR/globalHeader.ts | 18 + web/src/locales/pt-BR/menu.ts | 54 + web/src/locales/pt-BR/pages.ts | 80 ++ web/src/locales/pt-BR/pwa.ts | 7 + web/src/locales/pt-BR/settingDrawer.ts | 33 + web/src/locales/pt-BR/settings.ts | 66 + web/src/locales/zh-CN.ts | 24 + web/src/locales/zh-CN/component.ts | 5 + web/src/locales/zh-CN/globalHeader.ts | 17 + web/src/locales/zh-CN/menu.ts | 51 + web/src/locales/zh-CN/pages.ts | 80 ++ web/src/locales/zh-CN/pwa.ts | 6 + web/src/locales/zh-CN/settingDrawer.ts | 32 + web/src/locales/zh-CN/settings.ts | 59 + web/src/locales/zh-TW.ts | 22 + web/src/locales/zh-TW/component.ts | 5 + web/src/locales/zh-TW/globalHeader.ts | 17 + web/src/locales/zh-TW/menu.ts | 52 + web/src/locales/zh-TW/pages.ts | 73 ++ web/src/locales/zh-TW/pwa.ts | 6 + web/src/locales/zh-TW/settingDrawer.ts | 32 + web/src/locales/zh-TW/settings.ts | 59 + web/src/manifest.json | 22 + web/src/pages/404.tsx | 20 + web/src/pages/Admin.tsx | 51 + web/src/pages/Welcome.tsx | 166 +++ .../pages/admins/components/CreateForm.tsx | 149 +++ .../pages/admins/components/UpdateForm.tsx | 149 +++ web/src/pages/admins/index.tsx | 265 ++++ .../login/__snapshots__/login.test.tsx.snap | 1088 +++++++++++++++++ web/src/pages/user/login/index.tsx | 159 +++ web/src/pages/user/login/login.test.tsx | 102 ++ web/src/requestErrorConfig.ts | 54 + web/src/service-worker.js | 66 + web/src/services/index.ts | 60 + web/src/services/kratos/admin/v1/index.ts | 350 ++++++ web/src/typings.d.ts | 16 + web/tests/setupTests.jsx | 78 ++ web/tsconfig.json | 22 + web/types/index.d.ts | 120 ++ 183 files changed, 16186 insertions(+) create mode 100644 .codegraph/.gitignore create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 api/kratos/admin/v1/admin.pb.go create mode 100644 api/kratos/admin/v1/admin.proto create mode 100644 api/kratos/admin/v1/admin_grpc.pb.go create mode 100644 api/kratos/admin/v1/admin_http.pb.go create mode 100644 api/kratos/admin/v1/error_reason.pb.go create mode 100644 api/kratos/admin/v1/error_reason.proto create mode 100644 buf.gen.config.yaml create mode 100644 buf.gen.yaml create mode 100644 buf.lock create mode 100644 buf.yaml create mode 100644 configs/config.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 160000 gva create mode 100644 internal/biz/README.md create mode 100644 internal/biz/admin.go create mode 100644 internal/biz/admin_test.go create mode 100644 internal/biz/biz.go create mode 100644 internal/biz/errors.go create mode 100644 internal/biz/pagination.go create mode 100644 internal/conf/conf.pb.go create mode 100644 internal/conf/conf.proto create mode 100644 internal/data/README.md create mode 100644 internal/data/admin.go create mode 100644 internal/data/data.go create mode 100644 internal/data/ent/admin.go create mode 100644 internal/data/ent/admin/admin.go create mode 100644 internal/data/ent/admin/where.go create mode 100644 internal/data/ent/admin_create.go create mode 100644 internal/data/ent/admin_delete.go create mode 100644 internal/data/ent/admin_query.go create mode 100644 internal/data/ent/admin_update.go create mode 100644 internal/data/ent/client.go create mode 100644 internal/data/ent/ent.go create mode 100644 internal/data/ent/enttest/enttest.go create mode 100644 internal/data/ent/generate.go create mode 100644 internal/data/ent/hook/hook.go create mode 100644 internal/data/ent/migrate/migrate.go create mode 100644 internal/data/ent/migrate/schema.go create mode 100644 internal/data/ent/mutation.go create mode 100644 internal/data/ent/predicate/predicate.go create mode 100644 internal/data/ent/runtime.go create mode 100644 internal/data/ent/runtime/runtime.go create mode 100644 internal/data/ent/schema/admin.go create mode 100644 internal/data/ent/tx.go create mode 100644 internal/server/grpc.go create mode 100644 internal/server/http.go create mode 100644 internal/server/server.go create mode 100644 internal/service/README.md create mode 100644 internal/service/admin.go create mode 100644 internal/service/service.go create mode 100644 openapi.yaml create mode 100644 pkg/auth/auth.go create mode 100644 pkg/auth/config.go create mode 100644 pkg/auth/middleware.go create mode 100644 pkg/auth/middleware_test.go create mode 100644 pkg/auth/token.go create mode 100644 pkg/validate/validate.go create mode 100644 web/.commitlintrc.js create mode 100644 web/.editorconfig create mode 100644 web/.gitignore create mode 100644 web/.husky/commit-msg create mode 100644 web/.husky/pre-commit create mode 100644 web/.lintstagedrc create mode 100644 web/.npmrc create mode 100644 web/LICENSE create mode 100644 web/biome.json create mode 100644 web/config/config.ts create mode 100644 web/config/defaultSettings.ts create mode 100644 web/config/proxy.ts create mode 100644 web/config/routes.ts create mode 100644 web/jest.config.ts create mode 100644 web/package.json create mode 100644 web/public/CNAME create mode 100644 web/public/favicon.ico create mode 100644 web/public/icons/icon-128x128.png create mode 100644 web/public/icons/icon-192x192.png create mode 100644 web/public/icons/icon-512x512.png create mode 100644 web/public/logo.svg create mode 100644 web/public/pro_icon.svg create mode 100644 web/public/scripts/loading.js create mode 100644 web/src/access.ts create mode 100644 web/src/app.tsx create mode 100644 web/src/components/Footer/index.tsx create mode 100644 web/src/components/HeaderDropdown/index.tsx create mode 100644 web/src/components/RightContent/AvatarDropdown.tsx create mode 100644 web/src/components/RightContent/index.tsx create mode 100644 web/src/components/index.ts create mode 100644 web/src/global.less create mode 100644 web/src/global.style.ts create mode 100644 web/src/global.tsx create mode 100644 web/src/loading.tsx create mode 100644 web/src/locales/bn-BD.ts create mode 100644 web/src/locales/bn-BD/component.ts create mode 100644 web/src/locales/bn-BD/globalHeader.ts create mode 100644 web/src/locales/bn-BD/menu.ts create mode 100644 web/src/locales/bn-BD/pages.ts create mode 100644 web/src/locales/bn-BD/pwa.ts create mode 100644 web/src/locales/bn-BD/settingDrawer.ts create mode 100644 web/src/locales/bn-BD/settings.ts create mode 100644 web/src/locales/en-US.ts create mode 100644 web/src/locales/en-US/component.ts create mode 100644 web/src/locales/en-US/globalHeader.ts create mode 100644 web/src/locales/en-US/menu.ts create mode 100644 web/src/locales/en-US/pages.ts create mode 100644 web/src/locales/en-US/pwa.ts create mode 100644 web/src/locales/en-US/settingDrawer.ts create mode 100644 web/src/locales/en-US/settings.ts create mode 100644 web/src/locales/fa-IR.ts create mode 100644 web/src/locales/fa-IR/component.ts create mode 100644 web/src/locales/fa-IR/globalHeader.ts create mode 100644 web/src/locales/fa-IR/menu.ts create mode 100644 web/src/locales/fa-IR/pages.ts create mode 100644 web/src/locales/fa-IR/pwa.ts create mode 100644 web/src/locales/fa-IR/settingDrawer.ts create mode 100644 web/src/locales/fa-IR/settings.ts create mode 100644 web/src/locales/id-ID.ts create mode 100644 web/src/locales/id-ID/component.ts create mode 100644 web/src/locales/id-ID/globalHeader.ts create mode 100644 web/src/locales/id-ID/menu.ts create mode 100644 web/src/locales/id-ID/pages.ts create mode 100644 web/src/locales/id-ID/pwa.ts create mode 100644 web/src/locales/id-ID/settingDrawer.ts create mode 100644 web/src/locales/id-ID/settings.ts create mode 100644 web/src/locales/ja-JP.ts create mode 100644 web/src/locales/ja-JP/component.ts create mode 100644 web/src/locales/ja-JP/globalHeader.ts create mode 100644 web/src/locales/ja-JP/menu.ts create mode 100644 web/src/locales/ja-JP/pages.ts create mode 100644 web/src/locales/ja-JP/pwa.ts create mode 100644 web/src/locales/ja-JP/settingDrawer.ts create mode 100644 web/src/locales/ja-JP/settings.ts create mode 100644 web/src/locales/pt-BR.ts create mode 100644 web/src/locales/pt-BR/component.ts create mode 100644 web/src/locales/pt-BR/globalHeader.ts create mode 100644 web/src/locales/pt-BR/menu.ts create mode 100644 web/src/locales/pt-BR/pages.ts create mode 100644 web/src/locales/pt-BR/pwa.ts create mode 100644 web/src/locales/pt-BR/settingDrawer.ts create mode 100644 web/src/locales/pt-BR/settings.ts create mode 100644 web/src/locales/zh-CN.ts create mode 100644 web/src/locales/zh-CN/component.ts create mode 100644 web/src/locales/zh-CN/globalHeader.ts create mode 100644 web/src/locales/zh-CN/menu.ts create mode 100644 web/src/locales/zh-CN/pages.ts create mode 100644 web/src/locales/zh-CN/pwa.ts create mode 100644 web/src/locales/zh-CN/settingDrawer.ts create mode 100644 web/src/locales/zh-CN/settings.ts create mode 100644 web/src/locales/zh-TW.ts create mode 100644 web/src/locales/zh-TW/component.ts create mode 100644 web/src/locales/zh-TW/globalHeader.ts create mode 100644 web/src/locales/zh-TW/menu.ts create mode 100644 web/src/locales/zh-TW/pages.ts create mode 100644 web/src/locales/zh-TW/pwa.ts create mode 100644 web/src/locales/zh-TW/settingDrawer.ts create mode 100644 web/src/locales/zh-TW/settings.ts create mode 100644 web/src/manifest.json create mode 100644 web/src/pages/404.tsx create mode 100644 web/src/pages/Admin.tsx create mode 100644 web/src/pages/Welcome.tsx create mode 100644 web/src/pages/admins/components/CreateForm.tsx create mode 100644 web/src/pages/admins/components/UpdateForm.tsx create mode 100644 web/src/pages/admins/index.tsx create mode 100644 web/src/pages/user/login/__snapshots__/login.test.tsx.snap create mode 100644 web/src/pages/user/login/index.tsx create mode 100644 web/src/pages/user/login/login.test.tsx create mode 100644 web/src/requestErrorConfig.ts create mode 100644 web/src/service-worker.js create mode 100644 web/src/services/index.ts create mode 100644 web/src/services/kratos/admin/v1/index.ts create mode 100644 web/src/typings.d.ts create mode 100644 web/tests/setupTests.jsx create mode 100644 web/tsconfig.json create mode 100644 web/types/index.d.ts diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..d20c0fe --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..695cec3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +# Default to LF in the repo, let git convert on Windows checkout when needed. +* text=auto eol=lf + +# Go and proto sources are always LF. +*.go text eol=lf +*.proto text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.md text eol=lf + +# Generated files: keep diffs collapsed in review tools. +*.pb.go linguist-generated=true +*_grpc.pb.go linguist-generated=true +*_http.pb.go linguist-generated=true +wire_gen.go linguist-generated=true +openapi.yaml linguist-generated=true +internal/data/ent/** linguist-generated=true +web/src/services/** linguist-generated=true + +# Binaries. +*.png binary +*.jpg binary +*.ico binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5e2699 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Reference https://github.com/github/gitignore/blob/master/Go.gitignore +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Go workspace file +go.work + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# OS General +Thumbs.db +.DS_Store + +# project +*.cert +*.key +*.log +bin/ +/cmd/kratos-admin + +# Develop tools +.vscode/ +.cache/ +.idea/ +*.swp diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c198cb1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ +# Repository Guidelines + +This is a Kratos service template. This file owns the layering contract +agents must follow when changing the template. + +## Project structure + +``` +api/// Proto sources and generated stubs. Public contract. +cmd// Entrypoint, Wire injector, main.go. +configs/ Runtime config (config.yaml). No secrets. +internal/conf/ Config proto; generated by `make config`. +internal/server/ HTTP/gRPC server wiring. +internal/service/ Transport adapters; one file per resource. +internal/biz/ Domain models, usecases, repo interfaces, errors. +internal/data/ Repo implementations and storage clients. +``` + +## Layering & dependency rules + +Three model shapes flow through three layers. `biz` owns the DO, `data` +owns the PO; `service` is a pass-through that converts at its boundary. + +``` + client ──► DTO ──► service ──► DO ──► biz ──► DO ──► data ──► PO ──► storage + ▲ ▲ + │ declares │ implements + └─── repo IF ────┘ + + DTO Data Transfer Object — proto request / response. + DO Domain Object — pure biz model, no proto, no storage tags. + PO Persistent Object — storage shape, owned by `data`. +``` + +| Layer | Owns | Speaks at boundary | Never speaks | +|---------|------|--------------------|-------------------------| +| service | — | DTO ↔ DO | PO, storage client | +| biz | DO | DO | DTO, PO, storage client | +| data | PO | DO ↔ PO | DTO | + +- `service` imports `api/...` (DTO) and `biz` (DO). Never `data`. +- `biz` imports `api/...` only for error reason enums. Never `service`, + never `data`. The repo interface declared here is the inversion seam. +- `data` imports `biz` to implement the repo interface. Never `service`, + never DTOs. +- `cmd` is the only place that wires all layers via Wire. + +A change crossing these arrows the wrong way is a layering bug; fix the +design rather than add the import. + +### Layer responsibilities + +**service (DTO ↔ DO)** + +- `convert` parses an incoming proto into a DO. The reverse + direction is built inline at the return site; the reply type is + whatever the proto declares — usually the resource itself + (`return &v1.{...}, nil`), sometimes a list wrapper + (`*v1.Set`), or `&emptypb.Empty{}` for deletes. Inlining + keeps each handler self-contained. +- Embed `UnimplementedServiceServer`. +- Parse AIP list requests via `filtering` / `ordering` / `pagination`; + apply `fieldmask.Update` for partial updates. +- Validate request inputs at the service boundary before delegating to the + usecase. +- Return `biz` errors. No business rules, no storage access, no PO. + +**biz (DO only)** + +- Owns the DO (`type struct` — no proto, no storage tags), + the usecase, and the repo interface (`type Repo interface`). +- Owns typed errors built with `errors.NotFound` / `errors.BadRequest` + plus the API error reason enum. +- Owns `ListOption` helpers — `ListFilter`, `ListOrderBy`, `ListOffset`, + `ListLimit` — so callers compose queries without leaking storage + primitives. + +**data (DO ↔ PO)** + +- _Repo shape_: implement `biz.Repo`. The constructor returns + the interface, never the concrete type: + `func NewRepo(d *Data) biz.Repo`. +- _PO and conversion_: define a PO when the storage shape diverges from + the DO. PO types stay inside `data`. Use free functions + `new` (DO → PO, write) and `toBiz` (PO → DO, read). + Driver-specific builder types never leave `data`. +- _Shared clients_: `*Data` (declared in `internal/data/data.go`) holds + long-lived storage clients. Repos receive `*Data` and never construct + their own clients. +- _Querying_: translate `ListOptions.Filter` and `ListOptions.OrderBy` + into the storage driver's query language inside the repo. +- _Errors_: map driver errors to `biz` typed errors so callers above + never branch on the driver. + +**server** + +- Construct HTTP/gRPC servers, apply middleware, register services. No + translation, no business logic. + +### Add-a-resource checklist + +1. **DTO**: define `Create` / `Get` / + `List` / `Update` / `Delete` in + `api///`, then `make api`. +2. **DO + repo interface**: declare both in `biz`; build the usecase on + top of the interface. +3. **Repo impl**: implement in `data` returning `biz.Repo`; + add a PO and the matching conversion helpers when storage shape + diverges from DO. +4. **Wiring**: register the repo constructor in `data.ProviderSet`, the + usecase in `biz.ProviderSet`, the service in `service.ProviderSet`; + register HTTP/gRPC services in `internal/server`. +5. **Regenerate**: `make all` to refresh Wire and `go.mod`. + +### Testing seam + +Tests live beside the code they cover (`*_test.go`). Test layers in +isolation: service tests fake the usecase, biz tests fake the repo, data +tests exercise repo implementations at the storage boundary. + +## Generation & generated files + +Regenerate via `make api`, `make config`, or `make all`; never hand-edit +`*.pb.go`, `*_grpc.pb.go`, `*_http.pb.go`, or `wire_gen.go`. + +## Naming & error reasons + +- Resource: `` (e.g., `Todo`); collection RPC: + `List`. +- Types: repo `Repo`, usecase `Usecase`, service + `Service`. PO types live inside `internal/data/`; pick a + name that fits the storage driver and convert with + `new(do)` / `toBiz(po)` free functions. +- Error reasons: declared in `api///error_reason.proto`, + surfaced as `Err` in `biz`. + +## Commits & security + +- Conventional Commits: `feat:`, `fix:`, `refactor:`, `chore(deps):`, + `docs:`, `test:`. Regenerated files belong in the same commit as + their source. +- Never commit real credentials in `configs/config.yaml`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c198cb1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,142 @@ +# Repository Guidelines + +This is a Kratos service template. This file owns the layering contract +agents must follow when changing the template. + +## Project structure + +``` +api/// Proto sources and generated stubs. Public contract. +cmd// Entrypoint, Wire injector, main.go. +configs/ Runtime config (config.yaml). No secrets. +internal/conf/ Config proto; generated by `make config`. +internal/server/ HTTP/gRPC server wiring. +internal/service/ Transport adapters; one file per resource. +internal/biz/ Domain models, usecases, repo interfaces, errors. +internal/data/ Repo implementations and storage clients. +``` + +## Layering & dependency rules + +Three model shapes flow through three layers. `biz` owns the DO, `data` +owns the PO; `service` is a pass-through that converts at its boundary. + +``` + client ──► DTO ──► service ──► DO ──► biz ──► DO ──► data ──► PO ──► storage + ▲ ▲ + │ declares │ implements + └─── repo IF ────┘ + + DTO Data Transfer Object — proto request / response. + DO Domain Object — pure biz model, no proto, no storage tags. + PO Persistent Object — storage shape, owned by `data`. +``` + +| Layer | Owns | Speaks at boundary | Never speaks | +|---------|------|--------------------|-------------------------| +| service | — | DTO ↔ DO | PO, storage client | +| biz | DO | DO | DTO, PO, storage client | +| data | PO | DO ↔ PO | DTO | + +- `service` imports `api/...` (DTO) and `biz` (DO). Never `data`. +- `biz` imports `api/...` only for error reason enums. Never `service`, + never `data`. The repo interface declared here is the inversion seam. +- `data` imports `biz` to implement the repo interface. Never `service`, + never DTOs. +- `cmd` is the only place that wires all layers via Wire. + +A change crossing these arrows the wrong way is a layering bug; fix the +design rather than add the import. + +### Layer responsibilities + +**service (DTO ↔ DO)** + +- `convert` parses an incoming proto into a DO. The reverse + direction is built inline at the return site; the reply type is + whatever the proto declares — usually the resource itself + (`return &v1.{...}, nil`), sometimes a list wrapper + (`*v1.Set`), or `&emptypb.Empty{}` for deletes. Inlining + keeps each handler self-contained. +- Embed `UnimplementedServiceServer`. +- Parse AIP list requests via `filtering` / `ordering` / `pagination`; + apply `fieldmask.Update` for partial updates. +- Validate request inputs at the service boundary before delegating to the + usecase. +- Return `biz` errors. No business rules, no storage access, no PO. + +**biz (DO only)** + +- Owns the DO (`type struct` — no proto, no storage tags), + the usecase, and the repo interface (`type Repo interface`). +- Owns typed errors built with `errors.NotFound` / `errors.BadRequest` + plus the API error reason enum. +- Owns `ListOption` helpers — `ListFilter`, `ListOrderBy`, `ListOffset`, + `ListLimit` — so callers compose queries without leaking storage + primitives. + +**data (DO ↔ PO)** + +- _Repo shape_: implement `biz.Repo`. The constructor returns + the interface, never the concrete type: + `func NewRepo(d *Data) biz.Repo`. +- _PO and conversion_: define a PO when the storage shape diverges from + the DO. PO types stay inside `data`. Use free functions + `new` (DO → PO, write) and `toBiz` (PO → DO, read). + Driver-specific builder types never leave `data`. +- _Shared clients_: `*Data` (declared in `internal/data/data.go`) holds + long-lived storage clients. Repos receive `*Data` and never construct + their own clients. +- _Querying_: translate `ListOptions.Filter` and `ListOptions.OrderBy` + into the storage driver's query language inside the repo. +- _Errors_: map driver errors to `biz` typed errors so callers above + never branch on the driver. + +**server** + +- Construct HTTP/gRPC servers, apply middleware, register services. No + translation, no business logic. + +### Add-a-resource checklist + +1. **DTO**: define `Create` / `Get` / + `List` / `Update` / `Delete` in + `api///`, then `make api`. +2. **DO + repo interface**: declare both in `biz`; build the usecase on + top of the interface. +3. **Repo impl**: implement in `data` returning `biz.Repo`; + add a PO and the matching conversion helpers when storage shape + diverges from DO. +4. **Wiring**: register the repo constructor in `data.ProviderSet`, the + usecase in `biz.ProviderSet`, the service in `service.ProviderSet`; + register HTTP/gRPC services in `internal/server`. +5. **Regenerate**: `make all` to refresh Wire and `go.mod`. + +### Testing seam + +Tests live beside the code they cover (`*_test.go`). Test layers in +isolation: service tests fake the usecase, biz tests fake the repo, data +tests exercise repo implementations at the storage boundary. + +## Generation & generated files + +Regenerate via `make api`, `make config`, or `make all`; never hand-edit +`*.pb.go`, `*_grpc.pb.go`, `*_http.pb.go`, or `wire_gen.go`. + +## Naming & error reasons + +- Resource: `` (e.g., `Todo`); collection RPC: + `List`. +- Types: repo `Repo`, usecase `Usecase`, service + `Service`. PO types live inside `internal/data/`; pick a + name that fits the storage driver and convert with + `new(do)` / `toBiz(po)` free functions. +- Error reasons: declared in `api///error_reason.proto`, + surfaced as `Err` in `biz`. + +## Commits & security + +- Conventional Commits: `feat:`, `fix:`, `refactor:`, `chore(deps):`, + `docs:`, `test:`. Regenerated files belong in the same commit as + their source. +- Never commit real credentials in `configs/config.yaml`. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b2ba6a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1.25 AS builder + +COPY . /src +WORKDIR /src + +RUN GOPROXY=https://goproxy.cn make build + +FROM debian:stable-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + netbase \ + && rm -rf /var/lib/apt/lists/ \ + && apt-get autoremove -y && apt-get autoclean -y + +COPY --from=builder /src/bin /app + +WORKDIR /app + +EXPOSE 8000 +EXPOSE 9000 +VOLUME /data/conf + +CMD ["./kratos-admin", "-conf", "/data/conf"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..684318c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 go-kratos + +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/Makefile b/Makefile new file mode 100644 index 0000000..d47943e --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +GOHOSTOS:=$(shell go env GOHOSTOS) +GOPATH:=$(shell go env GOPATH) +VERSION=$(shell git describe --tags --always) + +.PHONY: init +# init env +init: + go install github.com/google/wire/cmd/wire@latest + go install github.com/bufbuild/buf/cmd/buf@latest + +.PHONY: config +# generate internal proto +config: + buf generate --template buf.gen.config.yaml + +.PHONY: api +# generate api proto +api: + buf generate --template buf.gen.yaml + +.PHONY: build +# build +build: + mkdir -p bin/ && go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./... + +.PHONY: generate +# generate +generate: + go generate ./... + go mod tidy + +.PHONY: all +# generate all +all: + make api + make config + make generate + +# show help +help: + @echo '' + @echo 'Usage:' + @echo ' make [target]' + @echo '' + @echo 'Targets:' + @awk '/^[a-zA-Z\-\_0-9]+:/ { \ + helpMessage = match(lastLine, /^# (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")); \ + helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ + printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + +.DEFAULT_GOAL := help diff --git a/README.md b/README.md new file mode 100644 index 0000000..65b2e0d --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Kratos Admin Template + +## Best Practice +Google AIP(https://google.aip.dev/general): +1. Resource-oriented design +2. Filtering +3. Pagination +4. Field masks +5. Field behavior + +## Generate API files +Proto generation is driven by [buf](https://buf.build). Dependencies such as +googleapis are resolved from the Buf Schema Registry (`buf.build/googleapis/googleapis`), +so there is no vendored `third_party/` directory. +```shell +# Install tooling (buf + wire) +make init +# Generate API files (pb.go, grpc, kratos http, openapi.yaml, web TS clients) from api/*.proto +make api +# Generate internal config (internal/conf/conf.pb.go) from internal/*.proto +make config +``` + +## Run Web Application +```shell +# Enter web directory, install dependencies and start development server +cd web +npm install +npm run dev +``` + +The generated clients work with any Promise-based HTTP client that returns JSON. +Services are defined and re-exported from this file: `web/src/services/index.ts`. +```typescript +import { createAdminServiceClient } from "@/services/kratos/admin/v1/index"; + +type Request = { + path: string; + method: string; + body: string | null; +}; + +function requestHandler({ path, method, body }: Request) { ... } + +export function createAdminService() { + return createAdminServiceClient(requestHandler); +} +``` + +Example using the generated client: +```typescript +import { createAdminService } from "@/services/index"; + +const adminService = createAdminService(); + +const handleLogin = async (username: string, password: string) => { + try { + const response = await adminService.Login({ + username: username, + password: password, + }); + console.log("Login successful:", response); + } catch (error) { + console.error("Login failed:", error); + } +}; +``` diff --git a/api/kratos/admin/v1/admin.pb.go b/api/kratos/admin/v1/admin.pb.go new file mode 100644 index 0000000..1139fd5 --- /dev/null +++ b/api/kratos/admin/v1/admin.pb.go @@ -0,0 +1,710 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kratos/admin/v1/admin.proto + +package v1 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Admin is the admin message. +type Admin struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The unique ID of the user. + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the user. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // The email of the user. + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + // The phone number of the user. + Phone string `protobuf:"bytes,4,opt,name=phone,proto3" json:"phone,omitempty"` + // The avatar URL of the user. + Avatar string `protobuf:"bytes,5,opt,name=avatar,proto3" json:"avatar,omitempty"` + // The access level of the user. + // Possible values are: "admin", "user", etc. + Access string `protobuf:"bytes,6,opt,name=access,proto3" json:"access,omitempty"` + // The password of the user. + Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"` + // The timestamp at which the user was created. + CreateTime *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` + // The latest timestamp at which the user was updated. + UpdateTime *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Admin) Reset() { + *x = Admin{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Admin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Admin) ProtoMessage() {} + +func (x *Admin) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Admin.ProtoReflect.Descriptor instead. +func (*Admin) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{0} +} + +func (x *Admin) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Admin) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Admin) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *Admin) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *Admin) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *Admin) GetAccess() string { + if x != nil { + return x.Access + } + return "" +} + +func (x *Admin) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *Admin) GetCreateTime() *timestamppb.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Admin) GetUpdateTime() *timestamppb.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +// AdminSet is the set of admins. +type AdminSet struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The set of admins. + Admins []*Admin `protobuf:"bytes,1,rep,name=admins,proto3" json:"admins,omitempty"` + // The next page token. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminSet) Reset() { + *x = AdminSet{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminSet) ProtoMessage() {} + +func (x *AdminSet) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminSet.ProtoReflect.Descriptor instead. +func (*AdminSet) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{1} +} + +func (x *AdminSet) GetAdmins() []*Admin { + if x != nil { + return x.Admins + } + return nil +} + +func (x *AdminSet) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// LoginRequest is the request message for the Login method. +type LoginRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. Password of the user. + Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` + // Required. Exactly one identity must be set. + // + // Types that are valid to be assigned to Identity: + // + // *LoginRequest_Username + // *LoginRequest_Email + Identity isLoginRequest_Identity `protobuf_oneof:"identity"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{2} +} + +func (x *LoginRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *LoginRequest) GetIdentity() isLoginRequest_Identity { + if x != nil { + return x.Identity + } + return nil +} + +func (x *LoginRequest) GetUsername() string { + if x != nil { + if x, ok := x.Identity.(*LoginRequest_Username); ok { + return x.Username + } + } + return "" +} + +func (x *LoginRequest) GetEmail() string { + if x != nil { + if x, ok := x.Identity.(*LoginRequest_Email); ok { + return x.Email + } + } + return "" +} + +type isLoginRequest_Identity interface { + isLoginRequest_Identity() +} + +type LoginRequest_Username struct { + Username string `protobuf:"bytes,2,opt,name=username,proto3,oneof"` +} + +type LoginRequest_Email struct { + Email string `protobuf:"bytes,3,opt,name=email,proto3,oneof"` +} + +func (*LoginRequest_Username) isLoginRequest_Identity() {} + +func (*LoginRequest_Email) isLoginRequest_Identity() {} + +// GetAdminRequest is the request message for the GetAdmin method. +type GetAdminRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The ID of the admin to retrieve. + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAdminRequest) Reset() { + *x = GetAdminRequest{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAdminRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAdminRequest) ProtoMessage() {} + +func (x *GetAdminRequest) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAdminRequest.ProtoReflect.Descriptor instead. +func (*GetAdminRequest) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{3} +} + +func (x *GetAdminRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +// ListAdminsResponse is the response message for the ListAdmins method. +type ListAdminsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional. The number of admins per page. + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Optional. The page token. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Optional. The standard list filter. + // Supported fields: + // - `name` (i.e. `name="John Doe"`) + // - `email` (i.e. `email="admin@go-kratos.dev"`) + // - `phone` (i.e. `phone="+1234567890"`) + // - `create_time` range (i.e. `timestamp>="2025-01-31T11:30:00-04:00"` where + // the timestamp is in RFC 3339 format) + // + // More detail in [AIP-160](https://google.aip.dev/160). + Filter string `protobuf:"bytes,3,opt,name=filter,proto3" json:"filter,omitempty"` + // Optional. A comma-separated list of fields to order by, sorted in ascending + // order. Use "desc" after a field name for descending. Supported fields: + // - `create_time` + // + // Example: `create_time desc`. + OrderBy string `protobuf:"bytes,4,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAdminsRequest) Reset() { + *x = ListAdminsRequest{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdminsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdminsRequest) ProtoMessage() {} + +func (x *ListAdminsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdminsRequest.ProtoReflect.Descriptor instead. +func (*ListAdminsRequest) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{4} +} + +func (x *ListAdminsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListAdminsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +func (x *ListAdminsRequest) GetFilter() string { + if x != nil { + return x.Filter + } + return "" +} + +func (x *ListAdminsRequest) GetOrderBy() string { + if x != nil { + return x.OrderBy + } + return "" +} + +// CreateAdminRequest is the request message for the CreateAdmin method. +type CreateAdminRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. The admin to create. + Admin *Admin `protobuf:"bytes,1,opt,name=admin,proto3" json:"admin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAdminRequest) Reset() { + *x = CreateAdminRequest{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAdminRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAdminRequest) ProtoMessage() {} + +func (x *CreateAdminRequest) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAdminRequest.ProtoReflect.Descriptor instead. +func (*CreateAdminRequest) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateAdminRequest) GetAdmin() *Admin { + if x != nil { + return x.Admin + } + return nil +} + +// UpdateAdminRequest is the request message for the UpdateAdmin method. +type UpdateAdminRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. The admin to update. + Admin *Admin `protobuf:"bytes,1,opt,name=admin,proto3" json:"admin,omitempty"` + // Required. Mask of fields to update. + UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAdminRequest) Reset() { + *x = UpdateAdminRequest{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAdminRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAdminRequest) ProtoMessage() {} + +func (x *UpdateAdminRequest) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAdminRequest.ProtoReflect.Descriptor instead. +func (*UpdateAdminRequest) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateAdminRequest) GetAdmin() *Admin { + if x != nil { + return x.Admin + } + return nil +} + +func (x *UpdateAdminRequest) GetUpdateMask() *fieldmaskpb.FieldMask { + if x != nil { + return x.UpdateMask + } + return nil +} + +// DeleteAdminRequest is the request message for the DeleteAdmin method. +type DeleteAdminRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. The ID of the admin to delete. + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAdminRequest) Reset() { + *x = DeleteAdminRequest{} + mi := &file_kratos_admin_v1_admin_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAdminRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAdminRequest) ProtoMessage() {} + +func (x *DeleteAdminRequest) ProtoReflect() protoreflect.Message { + mi := &file_kratos_admin_v1_admin_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAdminRequest.ProtoReflect.Descriptor instead. +func (*DeleteAdminRequest) Descriptor() ([]byte, []int) { + return file_kratos_admin_v1_admin_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteAdminRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +var File_kratos_admin_v1_admin_proto protoreflect.FileDescriptor + +const file_kratos_admin_v1_admin_proto_rawDesc = "" + + "\n" + + "\x1bkratos/admin/v1/admin.proto\x12\x0fkratos.admin.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a google/protobuf/field_mask.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\"\x9d\x02\n" + + "\x05Admin\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + + "\x05email\x18\x03 \x01(\tR\x05email\x12\x14\n" + + "\x05phone\x18\x04 \x01(\tR\x05phone\x12\x16\n" + + "\x06avatar\x18\x05 \x01(\tR\x06avatar\x12\x16\n" + + "\x06access\x18\x06 \x01(\tR\x06access\x12\x1a\n" + + "\bpassword\x18\a \x01(\tR\bpassword\x12;\n" + + "\vcreate_time\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "createTime\x12;\n" + + "\vupdate_time\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "updateTime\"b\n" + + "\bAdminSet\x12.\n" + + "\x06admins\x18\x01 \x03(\v2\x16.kratos.admin.v1.AdminR\x06admins\x12&\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"q\n" + + "\fLoginRequest\x12\x1f\n" + + "\bpassword\x18\x01 \x01(\tB\x03\xe0A\x02R\bpassword\x12\x1c\n" + + "\busername\x18\x02 \x01(\tH\x00R\busername\x12\x16\n" + + "\x05email\x18\x03 \x01(\tH\x00R\x05emailB\n" + + "\n" + + "\bidentity\"&\n" + + "\x0fGetAdminRequest\x12\x13\n" + + "\x02id\x18\x01 \x01(\x03B\x03\xe0A\x02R\x02id\"\x82\x01\n" + + "\x11ListAdminsRequest\x12\x1b\n" + + "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x02 \x01(\tR\tpageToken\x12\x16\n" + + "\x06filter\x18\x03 \x01(\tR\x06filter\x12\x19\n" + + "\border_by\x18\x04 \x01(\tR\aorderBy\"G\n" + + "\x12CreateAdminRequest\x121\n" + + "\x05admin\x18\x01 \x01(\v2\x16.kratos.admin.v1.AdminB\x03\xe0A\x02R\x05admin\"\x89\x01\n" + + "\x12UpdateAdminRequest\x121\n" + + "\x05admin\x18\x01 \x01(\v2\x16.kratos.admin.v1.AdminB\x03\xe0A\x02R\x05admin\x12@\n" + + "\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskB\x03\xe0A\x02R\n" + + "updateMask\")\n" + + "\x12DeleteAdminRequest\x12\x13\n" + + "\x02id\x18\x01 \x01(\x03B\x03\xe0A\x02R\x02id2\xa0\x06\n" + + "\fAdminService\x12[\n" + + "\x05Login\x12\x1d.kratos.admin.v1.LoginRequest\x1a\x16.kratos.admin.v1.Admin\"\x1b\x82\xd3\xe4\x93\x02\x15:\x01*\"\x10/v1/admins/login\x12V\n" + + "\x06Logout\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x1c\x82\xd3\xe4\x93\x02\x16:\x01*\"\x11/v1/admins/logout\x12U\n" + + "\aCurrent\x12\x16.google.protobuf.Empty\x1a\x16.kratos.admin.v1.Admin\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/admins/current\x12d\n" + + "\n" + + "ListAdmins\x12\".kratos.admin.v1.ListAdminsRequest\x1a\x19.kratos.admin.v1.AdminSet\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/admins/list\x12l\n" + + "\vCreateAdmin\x12#.kratos.admin.v1.CreateAdminRequest\x1a\x16.kratos.admin.v1.Admin\" \x82\xd3\xe4\x93\x02\x1a:\x05admin\"\x11/v1/admins/create\x12l\n" + + "\vUpdateAdmin\x12#.kratos.admin.v1.UpdateAdminRequest\x1a\x16.kratos.admin.v1.Admin\" \x82\xd3\xe4\x93\x02\x1a:\x05admin\x1a\x11/v1/admins/update\x12c\n" + + "\vDeleteAdmin\x12#.kratos.admin.v1.DeleteAdminRequest\x1a\x16.google.protobuf.Empty\"\x17\x82\xd3\xe4\x93\x02\x11*\x0f/v1/admins/{id}\x12]\n" + + "\bGetAdmin\x12 .kratos.admin.v1.GetAdminRequest\x1a\x16.kratos.admin.v1.Admin\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/admins/{id}BQ\n" + + "\x13api.kratos.admin.v1P\x01Z8kra/api/kratos/admin/v1;v1b\x06proto3" + +var ( + file_kratos_admin_v1_admin_proto_rawDescOnce sync.Once + file_kratos_admin_v1_admin_proto_rawDescData []byte +) + +func file_kratos_admin_v1_admin_proto_rawDescGZIP() []byte { + file_kratos_admin_v1_admin_proto_rawDescOnce.Do(func() { + file_kratos_admin_v1_admin_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kratos_admin_v1_admin_proto_rawDesc), len(file_kratos_admin_v1_admin_proto_rawDesc))) + }) + return file_kratos_admin_v1_admin_proto_rawDescData +} + +var file_kratos_admin_v1_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_kratos_admin_v1_admin_proto_goTypes = []any{ + (*Admin)(nil), // 0: kratos.admin.v1.Admin + (*AdminSet)(nil), // 1: kratos.admin.v1.AdminSet + (*LoginRequest)(nil), // 2: kratos.admin.v1.LoginRequest + (*GetAdminRequest)(nil), // 3: kratos.admin.v1.GetAdminRequest + (*ListAdminsRequest)(nil), // 4: kratos.admin.v1.ListAdminsRequest + (*CreateAdminRequest)(nil), // 5: kratos.admin.v1.CreateAdminRequest + (*UpdateAdminRequest)(nil), // 6: kratos.admin.v1.UpdateAdminRequest + (*DeleteAdminRequest)(nil), // 7: kratos.admin.v1.DeleteAdminRequest + (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp + (*fieldmaskpb.FieldMask)(nil), // 9: google.protobuf.FieldMask + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty +} +var file_kratos_admin_v1_admin_proto_depIdxs = []int32{ + 8, // 0: kratos.admin.v1.Admin.create_time:type_name -> google.protobuf.Timestamp + 8, // 1: kratos.admin.v1.Admin.update_time:type_name -> google.protobuf.Timestamp + 0, // 2: kratos.admin.v1.AdminSet.admins:type_name -> kratos.admin.v1.Admin + 0, // 3: kratos.admin.v1.CreateAdminRequest.admin:type_name -> kratos.admin.v1.Admin + 0, // 4: kratos.admin.v1.UpdateAdminRequest.admin:type_name -> kratos.admin.v1.Admin + 9, // 5: kratos.admin.v1.UpdateAdminRequest.update_mask:type_name -> google.protobuf.FieldMask + 2, // 6: kratos.admin.v1.AdminService.Login:input_type -> kratos.admin.v1.LoginRequest + 10, // 7: kratos.admin.v1.AdminService.Logout:input_type -> google.protobuf.Empty + 10, // 8: kratos.admin.v1.AdminService.Current:input_type -> google.protobuf.Empty + 4, // 9: kratos.admin.v1.AdminService.ListAdmins:input_type -> kratos.admin.v1.ListAdminsRequest + 5, // 10: kratos.admin.v1.AdminService.CreateAdmin:input_type -> kratos.admin.v1.CreateAdminRequest + 6, // 11: kratos.admin.v1.AdminService.UpdateAdmin:input_type -> kratos.admin.v1.UpdateAdminRequest + 7, // 12: kratos.admin.v1.AdminService.DeleteAdmin:input_type -> kratos.admin.v1.DeleteAdminRequest + 3, // 13: kratos.admin.v1.AdminService.GetAdmin:input_type -> kratos.admin.v1.GetAdminRequest + 0, // 14: kratos.admin.v1.AdminService.Login:output_type -> kratos.admin.v1.Admin + 10, // 15: kratos.admin.v1.AdminService.Logout:output_type -> google.protobuf.Empty + 0, // 16: kratos.admin.v1.AdminService.Current:output_type -> kratos.admin.v1.Admin + 1, // 17: kratos.admin.v1.AdminService.ListAdmins:output_type -> kratos.admin.v1.AdminSet + 0, // 18: kratos.admin.v1.AdminService.CreateAdmin:output_type -> kratos.admin.v1.Admin + 0, // 19: kratos.admin.v1.AdminService.UpdateAdmin:output_type -> kratos.admin.v1.Admin + 10, // 20: kratos.admin.v1.AdminService.DeleteAdmin:output_type -> google.protobuf.Empty + 0, // 21: kratos.admin.v1.AdminService.GetAdmin:output_type -> kratos.admin.v1.Admin + 14, // [14:22] is the sub-list for method output_type + 6, // [6:14] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_kratos_admin_v1_admin_proto_init() } +func file_kratos_admin_v1_admin_proto_init() { + if File_kratos_admin_v1_admin_proto != nil { + return + } + file_kratos_admin_v1_admin_proto_msgTypes[2].OneofWrappers = []any{ + (*LoginRequest_Username)(nil), + (*LoginRequest_Email)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kratos_admin_v1_admin_proto_rawDesc), len(file_kratos_admin_v1_admin_proto_rawDesc)), + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kratos_admin_v1_admin_proto_goTypes, + DependencyIndexes: file_kratos_admin_v1_admin_proto_depIdxs, + MessageInfos: file_kratos_admin_v1_admin_proto_msgTypes, + }.Build() + File_kratos_admin_v1_admin_proto = out.File + file_kratos_admin_v1_admin_proto_goTypes = nil + file_kratos_admin_v1_admin_proto_depIdxs = nil +} diff --git a/api/kratos/admin/v1/admin.proto b/api/kratos/admin/v1/admin.proto new file mode 100644 index 0000000..3685257 --- /dev/null +++ b/api/kratos/admin/v1/admin.proto @@ -0,0 +1,161 @@ +syntax = "proto3"; + +package kratos.admin.v1; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/field_mask.proto"; +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; + +option go_package = "kra/api/kratos/admin/v1;v1"; +option java_multiple_files = true; +option java_package = "api.kratos.admin.v1"; + +// Admin is the admin message. +message Admin { + // The unique ID of the user. + int64 id = 1; + // The name of the user. + string name = 2; + // The email of the user. + string email = 3; + // The phone number of the user. + string phone = 4; + // The avatar URL of the user. + string avatar = 5; + // The access level of the user. + // Possible values are: "admin", "user", etc. + string access = 6; + // The password of the user. + string password = 7; + // The timestamp at which the user was created. + google.protobuf.Timestamp create_time = 8; + // The latest timestamp at which the user was updated. + google.protobuf.Timestamp update_time = 9; +} + +// AdminSet is the set of admins. +message AdminSet { + // The set of admins. + repeated Admin admins = 1; + // The next page token. + string next_page_token = 2; +} + +// AdminService is the admin service definition. +service AdminService { + // Login a user and return the username. + rpc Login (LoginRequest) returns (Admin) { + option (google.api.http) = { + post: "/v1/admins/login" + body: "*" + }; + } + // Logout the currently logged-in user. + rpc Logout (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/admins/logout" + body: "*" + }; + } + // Current returns the currently logged-in user. + rpc Current (google.protobuf.Empty) returns (Admin) { + option (google.api.http) = { + get: "/v1/admins/current" + }; + } + // ListAdmins returns a list of admins. + rpc ListAdmins(ListAdminsRequest) returns (AdminSet) { + option (google.api.http) = { + get: "/v1/admins/list" + }; + } + // CreateAdmin creates a new admin. + rpc CreateAdmin(CreateAdminRequest) returns (Admin) { + option (google.api.http) = { + post: "/v1/admins/create" + body: "admin" + }; + } + // UpdateAdmin updates an existing admin. + rpc UpdateAdmin(UpdateAdminRequest) returns (Admin) { + option (google.api.http) = { + put: "/v1/admins/update" + body: "admin" + }; + } + // DeleteAdmin deletes an admin by ID. + rpc DeleteAdmin(DeleteAdminRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/admins/{id}" + }; + } + // GetAdmin retrieves an admin by ID. + rpc GetAdmin(GetAdminRequest) returns (Admin) { + option (google.api.http) = { + get: "/v1/admins/{id}" + }; + } +} + +// LoginRequest is the request message for the Login method. +message LoginRequest { + // Required. Password of the user. + string password = 1 [(google.api.field_behavior) = REQUIRED]; + // Required. Exactly one identity must be set. + oneof identity { + string username = 2; + string email = 3; + } +} + +// GetAdminRequest is the request message for the GetAdmin method. +message GetAdminRequest { + // The ID of the admin to retrieve. + int64 id = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// ListAdminsResponse is the response message for the ListAdmins method. +message ListAdminsRequest { + // Optional. The number of admins per page. + int32 page_size = 1; + // Optional. The page token. + string page_token = 2; + // Optional. The standard list filter. + // Supported fields: + // * `name` (i.e. `name="John Doe"`) + // * `email` (i.e. `email="admin@go-kratos.dev"`) + // * `phone` (i.e. `phone="+1234567890"`) + // * `create_time` range (i.e. `timestamp>="2025-01-31T11:30:00-04:00"` where + // the timestamp is in RFC 3339 format) + // + // More detail in [AIP-160](https://google.aip.dev/160). + string filter = 3; + // Optional. A comma-separated list of fields to order by, sorted in ascending + // order. Use "desc" after a field name for descending. Supported fields: + // - `create_time` + // + // Example: `create_time desc`. + string order_by = 4; +} + +// CreateAdminRequest is the request message for the CreateAdmin method. +message CreateAdminRequest { + // Required. The admin to create. + Admin admin = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// UpdateAdminRequest is the request message for the UpdateAdmin method. +message UpdateAdminRequest { + // Required. The admin to update. + Admin admin = 1 [(google.api.field_behavior) = REQUIRED]; + // Required. Mask of fields to update. + google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// DeleteAdminRequest is the request message for the DeleteAdmin method. +message DeleteAdminRequest { + // Required. The ID of the admin to delete. + int64 id = 1 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/api/kratos/admin/v1/admin_grpc.pb.go b/api/kratos/admin/v1/admin_grpc.pb.go new file mode 100644 index 0000000..9e9ecd1 --- /dev/null +++ b/api/kratos/admin/v1/admin_grpc.pb.go @@ -0,0 +1,408 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kratos/admin/v1/admin.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AdminService_Login_FullMethodName = "/kratos.admin.v1.AdminService/Login" + AdminService_Logout_FullMethodName = "/kratos.admin.v1.AdminService/Logout" + AdminService_Current_FullMethodName = "/kratos.admin.v1.AdminService/Current" + AdminService_ListAdmins_FullMethodName = "/kratos.admin.v1.AdminService/ListAdmins" + AdminService_CreateAdmin_FullMethodName = "/kratos.admin.v1.AdminService/CreateAdmin" + AdminService_UpdateAdmin_FullMethodName = "/kratos.admin.v1.AdminService/UpdateAdmin" + AdminService_DeleteAdmin_FullMethodName = "/kratos.admin.v1.AdminService/DeleteAdmin" + AdminService_GetAdmin_FullMethodName = "/kratos.admin.v1.AdminService/GetAdmin" +) + +// AdminServiceClient is the client API for AdminService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// AdminService is the admin service definition. +type AdminServiceClient interface { + // Login a user and return the username. + Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*Admin, error) + // Logout the currently logged-in user. + Logout(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + // Current returns the currently logged-in user. + Current(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Admin, error) + // ListAdmins returns a list of admins. + ListAdmins(ctx context.Context, in *ListAdminsRequest, opts ...grpc.CallOption) (*AdminSet, error) + // CreateAdmin creates a new admin. + CreateAdmin(ctx context.Context, in *CreateAdminRequest, opts ...grpc.CallOption) (*Admin, error) + // UpdateAdmin updates an existing admin. + UpdateAdmin(ctx context.Context, in *UpdateAdminRequest, opts ...grpc.CallOption) (*Admin, error) + // DeleteAdmin deletes an admin by ID. + DeleteAdmin(ctx context.Context, in *DeleteAdminRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // GetAdmin retrieves an admin by ID. + GetAdmin(ctx context.Context, in *GetAdminRequest, opts ...grpc.CallOption) (*Admin, error) +} + +type adminServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAdminServiceClient(cc grpc.ClientConnInterface) AdminServiceClient { + return &adminServiceClient{cc} +} + +func (c *adminServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*Admin, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Admin) + err := c.cc.Invoke(ctx, AdminService_Login_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) Logout(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, AdminService_Logout_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) Current(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Admin, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Admin) + err := c.cc.Invoke(ctx, AdminService_Current_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) ListAdmins(ctx context.Context, in *ListAdminsRequest, opts ...grpc.CallOption) (*AdminSet, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdminSet) + err := c.cc.Invoke(ctx, AdminService_ListAdmins_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) CreateAdmin(ctx context.Context, in *CreateAdminRequest, opts ...grpc.CallOption) (*Admin, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Admin) + err := c.cc.Invoke(ctx, AdminService_CreateAdmin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) UpdateAdmin(ctx context.Context, in *UpdateAdminRequest, opts ...grpc.CallOption) (*Admin, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Admin) + err := c.cc.Invoke(ctx, AdminService_UpdateAdmin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) DeleteAdmin(ctx context.Context, in *DeleteAdminRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, AdminService_DeleteAdmin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) GetAdmin(ctx context.Context, in *GetAdminRequest, opts ...grpc.CallOption) (*Admin, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Admin) + err := c.cc.Invoke(ctx, AdminService_GetAdmin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdminServiceServer is the server API for AdminService service. +// All implementations must embed UnimplementedAdminServiceServer +// for forward compatibility. +// +// AdminService is the admin service definition. +type AdminServiceServer interface { + // Login a user and return the username. + Login(context.Context, *LoginRequest) (*Admin, error) + // Logout the currently logged-in user. + Logout(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + // Current returns the currently logged-in user. + Current(context.Context, *emptypb.Empty) (*Admin, error) + // ListAdmins returns a list of admins. + ListAdmins(context.Context, *ListAdminsRequest) (*AdminSet, error) + // CreateAdmin creates a new admin. + CreateAdmin(context.Context, *CreateAdminRequest) (*Admin, error) + // UpdateAdmin updates an existing admin. + UpdateAdmin(context.Context, *UpdateAdminRequest) (*Admin, error) + // DeleteAdmin deletes an admin by ID. + DeleteAdmin(context.Context, *DeleteAdminRequest) (*emptypb.Empty, error) + // GetAdmin retrieves an admin by ID. + GetAdmin(context.Context, *GetAdminRequest) (*Admin, error) + mustEmbedUnimplementedAdminServiceServer() +} + +// UnimplementedAdminServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAdminServiceServer struct{} + +func (UnimplementedAdminServiceServer) Login(context.Context, *LoginRequest) (*Admin, error) { + return nil, status.Error(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedAdminServiceServer) Logout(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method Logout not implemented") +} +func (UnimplementedAdminServiceServer) Current(context.Context, *emptypb.Empty) (*Admin, error) { + return nil, status.Error(codes.Unimplemented, "method Current not implemented") +} +func (UnimplementedAdminServiceServer) ListAdmins(context.Context, *ListAdminsRequest) (*AdminSet, error) { + return nil, status.Error(codes.Unimplemented, "method ListAdmins not implemented") +} +func (UnimplementedAdminServiceServer) CreateAdmin(context.Context, *CreateAdminRequest) (*Admin, error) { + return nil, status.Error(codes.Unimplemented, "method CreateAdmin not implemented") +} +func (UnimplementedAdminServiceServer) UpdateAdmin(context.Context, *UpdateAdminRequest) (*Admin, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateAdmin not implemented") +} +func (UnimplementedAdminServiceServer) DeleteAdmin(context.Context, *DeleteAdminRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteAdmin not implemented") +} +func (UnimplementedAdminServiceServer) GetAdmin(context.Context, *GetAdminRequest) (*Admin, error) { + return nil, status.Error(codes.Unimplemented, "method GetAdmin not implemented") +} +func (UnimplementedAdminServiceServer) mustEmbedUnimplementedAdminServiceServer() {} +func (UnimplementedAdminServiceServer) testEmbeddedByValue() {} + +// UnsafeAdminServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AdminServiceServer will +// result in compilation errors. +type UnsafeAdminServiceServer interface { + mustEmbedUnimplementedAdminServiceServer() +} + +func RegisterAdminServiceServer(s grpc.ServiceRegistrar, srv AdminServiceServer) { + // If the following call panics, it indicates UnimplementedAdminServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AdminService_ServiceDesc, srv) +} + +func _AdminService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_Login_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).Login(ctx, req.(*LoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).Logout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_Logout_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).Logout(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_Current_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).Current(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_Current_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).Current(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_ListAdmins_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAdminsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).ListAdmins(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_ListAdmins_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).ListAdmins(ctx, req.(*ListAdminsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_CreateAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAdminRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).CreateAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_CreateAdmin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).CreateAdmin(ctx, req.(*CreateAdminRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_UpdateAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateAdminRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).UpdateAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_UpdateAdmin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).UpdateAdmin(ctx, req.(*UpdateAdminRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_DeleteAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAdminRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).DeleteAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_DeleteAdmin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).DeleteAdmin(ctx, req.(*DeleteAdminRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_GetAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAdminRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).GetAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_GetAdmin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).GetAdmin(ctx, req.(*GetAdminRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AdminService_ServiceDesc is the grpc.ServiceDesc for AdminService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AdminService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kratos.admin.v1.AdminService", + HandlerType: (*AdminServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Login", + Handler: _AdminService_Login_Handler, + }, + { + MethodName: "Logout", + Handler: _AdminService_Logout_Handler, + }, + { + MethodName: "Current", + Handler: _AdminService_Current_Handler, + }, + { + MethodName: "ListAdmins", + Handler: _AdminService_ListAdmins_Handler, + }, + { + MethodName: "CreateAdmin", + Handler: _AdminService_CreateAdmin_Handler, + }, + { + MethodName: "UpdateAdmin", + Handler: _AdminService_UpdateAdmin_Handler, + }, + { + MethodName: "DeleteAdmin", + Handler: _AdminService_DeleteAdmin_Handler, + }, + { + MethodName: "GetAdmin", + Handler: _AdminService_GetAdmin_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kratos/admin/v1/admin.proto", +} diff --git a/api/kratos/admin/v1/admin_http.pb.go b/api/kratos/admin/v1/admin_http.pb.go new file mode 100644 index 0000000..f496ac5 --- /dev/null +++ b/api/kratos/admin/v1/admin_http.pb.go @@ -0,0 +1,390 @@ +// Code generated by protoc-gen-go-http. DO NOT EDIT. +// versions: +// - protoc-gen-go-http v2.9.2 +// - protoc (unknown) +// source: kratos/admin/v1/admin.proto + +package v1 + +import ( + context "context" + http "github.com/go-kratos/kratos/v3/transport/http" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the kratos package it is being compiled against. +var _ = new(context.Context) + +const _ = http.SupportPackageIsVersion3 + +const OperationAdminServiceCreateAdmin = "/kratos.admin.v1.AdminService/CreateAdmin" +const OperationAdminServiceCurrent = "/kratos.admin.v1.AdminService/Current" +const OperationAdminServiceDeleteAdmin = "/kratos.admin.v1.AdminService/DeleteAdmin" +const OperationAdminServiceGetAdmin = "/kratos.admin.v1.AdminService/GetAdmin" +const OperationAdminServiceListAdmins = "/kratos.admin.v1.AdminService/ListAdmins" +const OperationAdminServiceLogin = "/kratos.admin.v1.AdminService/Login" +const OperationAdminServiceLogout = "/kratos.admin.v1.AdminService/Logout" +const OperationAdminServiceUpdateAdmin = "/kratos.admin.v1.AdminService/UpdateAdmin" + +type AdminServiceHTTPServer interface { + // CreateAdmin CreateAdmin creates a new admin. + CreateAdmin(context.Context, *CreateAdminRequest) (*Admin, error) + // Current Current returns the currently logged-in user. + Current(context.Context, *emptypb.Empty) (*Admin, error) + // DeleteAdmin DeleteAdmin deletes an admin by ID. + DeleteAdmin(context.Context, *DeleteAdminRequest) (*emptypb.Empty, error) + // GetAdmin GetAdmin retrieves an admin by ID. + GetAdmin(context.Context, *GetAdminRequest) (*Admin, error) + // ListAdmins ListAdmins returns a list of admins. + ListAdmins(context.Context, *ListAdminsRequest) (*AdminSet, error) + // Login Login a user and return the username. + Login(context.Context, *LoginRequest) (*Admin, error) + // Logout Logout the currently logged-in user. + Logout(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + // UpdateAdmin UpdateAdmin updates an existing admin. + UpdateAdmin(context.Context, *UpdateAdminRequest) (*Admin, error) +} + +func RegisterAdminServiceHTTPServer(s *http.Server, srv AdminServiceHTTPServer) { + r := s.Route("/") + r.Handle("POST", "/v1/admins/login", _AdminService_Login0_HTTP_Handler(srv)) + r.Handle("POST", "/v1/admins/logout", _AdminService_Logout0_HTTP_Handler(srv)) + r.Handle("GET", "/v1/admins/current", _AdminService_Current0_HTTP_Handler(srv)) + r.Handle("GET", "/v1/admins/list", _AdminService_ListAdmins0_HTTP_Handler(srv)) + r.Handle("POST", "/v1/admins/create", _AdminService_CreateAdmin0_HTTP_Handler(srv)) + r.Handle("PUT", "/v1/admins/update", _AdminService_UpdateAdmin0_HTTP_Handler(srv)) + r.Handle("DELETE", "/v1/admins/{id}", _AdminService_DeleteAdmin0_HTTP_Handler(srv)) + r.Handle("GET", "/v1/admins/{id}", _AdminService_GetAdmin0_HTTP_Handler(srv)) +} + +func _AdminService_Login0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in LoginRequest + if err := ctx.Bind(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceLogin) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.Login(ctx, req.(*LoginRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*Admin) + return ctx.Result(200, reply) + } +} + +func _AdminService_Logout0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in emptypb.Empty + if err := ctx.Bind(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceLogout) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.Logout(ctx, req.(*emptypb.Empty)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*emptypb.Empty) + return ctx.Result(200, reply) + } +} + +func _AdminService_Current0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in emptypb.Empty + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceCurrent) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.Current(ctx, req.(*emptypb.Empty)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*Admin) + return ctx.Result(200, reply) + } +} + +func _AdminService_ListAdmins0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in ListAdminsRequest + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceListAdmins) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.ListAdmins(ctx, req.(*ListAdminsRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*AdminSet) + return ctx.Result(200, reply) + } +} + +func _AdminService_CreateAdmin0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in CreateAdminRequest + if err := ctx.Bind(&in.Admin); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceCreateAdmin) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.CreateAdmin(ctx, req.(*CreateAdminRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*Admin) + return ctx.Result(200, reply) + } +} + +func _AdminService_UpdateAdmin0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in UpdateAdminRequest + if err := ctx.Bind(&in.Admin); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceUpdateAdmin) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.UpdateAdmin(ctx, req.(*UpdateAdminRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*Admin) + return ctx.Result(200, reply) + } +} + +func _AdminService_DeleteAdmin0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in DeleteAdminRequest + if err := ctx.BindQuery(&in); err != nil { + return err + } + if err := ctx.BindVars(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceDeleteAdmin) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.DeleteAdmin(ctx, req.(*DeleteAdminRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*emptypb.Empty) + return ctx.Result(200, reply) + } +} + +func _AdminService_GetAdmin0_HTTP_Handler(srv AdminServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetAdminRequest + if err := ctx.BindQuery(&in); err != nil { + return err + } + if err := ctx.BindVars(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationAdminServiceGetAdmin) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetAdmin(ctx, req.(*GetAdminRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*Admin) + return ctx.Result(200, reply) + } +} + +type AdminServiceHTTPClient interface { + // CreateAdmin CreateAdmin creates a new admin. + CreateAdmin(ctx context.Context, req *CreateAdminRequest, opts ...http.CallOption) (rsp *Admin, err error) + // Current Current returns the currently logged-in user. + Current(ctx context.Context, req *emptypb.Empty, opts ...http.CallOption) (rsp *Admin, err error) + // DeleteAdmin DeleteAdmin deletes an admin by ID. + DeleteAdmin(ctx context.Context, req *DeleteAdminRequest, opts ...http.CallOption) (rsp *emptypb.Empty, err error) + // GetAdmin GetAdmin retrieves an admin by ID. + GetAdmin(ctx context.Context, req *GetAdminRequest, opts ...http.CallOption) (rsp *Admin, err error) + // ListAdmins ListAdmins returns a list of admins. + ListAdmins(ctx context.Context, req *ListAdminsRequest, opts ...http.CallOption) (rsp *AdminSet, err error) + // Login Login a user and return the username. + Login(ctx context.Context, req *LoginRequest, opts ...http.CallOption) (rsp *Admin, err error) + // Logout Logout the currently logged-in user. + Logout(ctx context.Context, req *emptypb.Empty, opts ...http.CallOption) (rsp *emptypb.Empty, err error) + // UpdateAdmin UpdateAdmin updates an existing admin. + UpdateAdmin(ctx context.Context, req *UpdateAdminRequest, opts ...http.CallOption) (rsp *Admin, err error) +} + +type AdminServiceHTTPClientImpl struct { + cc *http.Client +} + +func NewAdminServiceHTTPClient(client *http.Client) AdminServiceHTTPClient { + return &AdminServiceHTTPClientImpl{client} +} + +// CreateAdmin CreateAdmin creates a new admin. +func (c *AdminServiceHTTPClientImpl) CreateAdmin(ctx context.Context, in *CreateAdminRequest, opts ...http.CallOption) (*Admin, error) { + var out Admin + pattern := "/v1/admins/create" + path := http.BuildPath(pattern, in, http.WithQueryParams(), http.WithOmitFields("admin")) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.ContentType("application/protojson"), + http.Operation(OperationAdminServiceCreateAdmin), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "POST", path, in.Admin, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// Current Current returns the currently logged-in user. +func (c *AdminServiceHTTPClientImpl) Current(ctx context.Context, in *emptypb.Empty, opts ...http.CallOption) (*Admin, error) { + var out Admin + pattern := "/v1/admins/current" + path := http.BuildPath(pattern, in, http.WithQueryParams()) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.Operation(OperationAdminServiceCurrent), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// DeleteAdmin DeleteAdmin deletes an admin by ID. +func (c *AdminServiceHTTPClientImpl) DeleteAdmin(ctx context.Context, in *DeleteAdminRequest, opts ...http.CallOption) (*emptypb.Empty, error) { + var out emptypb.Empty + pattern := "/v1/admins/{id}" + path := http.BuildPath(pattern, in, http.WithQueryParams()) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.Operation(OperationAdminServiceDeleteAdmin), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "DELETE", path, nil, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// GetAdmin GetAdmin retrieves an admin by ID. +func (c *AdminServiceHTTPClientImpl) GetAdmin(ctx context.Context, in *GetAdminRequest, opts ...http.CallOption) (*Admin, error) { + var out Admin + pattern := "/v1/admins/{id}" + path := http.BuildPath(pattern, in, http.WithQueryParams()) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.Operation(OperationAdminServiceGetAdmin), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// ListAdmins ListAdmins returns a list of admins. +func (c *AdminServiceHTTPClientImpl) ListAdmins(ctx context.Context, in *ListAdminsRequest, opts ...http.CallOption) (*AdminSet, error) { + var out AdminSet + pattern := "/v1/admins/list" + path := http.BuildPath(pattern, in, http.WithQueryParams()) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.Operation(OperationAdminServiceListAdmins), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// Login Login a user and return the username. +func (c *AdminServiceHTTPClientImpl) Login(ctx context.Context, in *LoginRequest, opts ...http.CallOption) (*Admin, error) { + var out Admin + pattern := "/v1/admins/login" + path := http.BuildPath(pattern, in) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.ContentType("application/protojson"), + http.Operation(OperationAdminServiceLogin), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// Logout Logout the currently logged-in user. +func (c *AdminServiceHTTPClientImpl) Logout(ctx context.Context, in *emptypb.Empty, opts ...http.CallOption) (*emptypb.Empty, error) { + var out emptypb.Empty + pattern := "/v1/admins/logout" + path := http.BuildPath(pattern, in) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.ContentType("application/protojson"), + http.Operation(OperationAdminServiceLogout), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// UpdateAdmin UpdateAdmin updates an existing admin. +func (c *AdminServiceHTTPClientImpl) UpdateAdmin(ctx context.Context, in *UpdateAdminRequest, opts ...http.CallOption) (*Admin, error) { + var out Admin + pattern := "/v1/admins/update" + path := http.BuildPath(pattern, in, http.WithQueryParams(), http.WithOmitFields("admin")) + opts = append([]http.CallOption{ + http.Accept("application/protojson"), + http.ContentType("application/protojson"), + http.Operation(OperationAdminServiceUpdateAdmin), + http.PathTemplate(pattern), + }, opts...) + err := c.cc.Invoke(ctx, "PUT", path, in.Admin, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/api/kratos/admin/v1/error_reason.pb.go b/api/kratos/admin/v1/error_reason.pb.go new file mode 100644 index 0000000..9764807 --- /dev/null +++ b/api/kratos/admin/v1/error_reason.pb.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kratos/admin/v1/error_reason.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ErrorReason is the stable vocabulary for the `reason` field of the error +// body. Values are wire-visible: protojson encodes them as their names, so +// clients branch on the name (e.g. "ADMIN_NOT_FOUND") rather than on an HTTP +// status. Surfaced in `biz` as `Err`. +type ErrorReason int32 + +const ( + // Unset. Never returned; reserved by proto3 for the zero value. + ErrorReason_ERROR_REASON_UNSPECIFIED ErrorReason = 0 + // The requested admin does not exist. + ErrorReason_ADMIN_NOT_FOUND ErrorReason = 1 + // Login failed. Returned for both an unknown account and a wrong password + // so callers cannot probe which accounts are registered. + ErrorReason_INVALID_CREDENTIALS ErrorReason = 2 + // The login request carried no identity, or one this server does not accept. + ErrorReason_INVALID_IDENTITY_TYPE ErrorReason = 3 + // The request carried no usable credential (missing or invalid token). + ErrorReason_UNAUTHENTICATED ErrorReason = 4 + // The caller is authenticated but lacks the required access level. + ErrorReason_PERMISSION_DENIED ErrorReason = 5 +) + +// Enum value maps for ErrorReason. +var ( + ErrorReason_name = map[int32]string{ + 0: "ERROR_REASON_UNSPECIFIED", + 1: "ADMIN_NOT_FOUND", + 2: "INVALID_CREDENTIALS", + 3: "INVALID_IDENTITY_TYPE", + 4: "UNAUTHENTICATED", + 5: "PERMISSION_DENIED", + } + ErrorReason_value = map[string]int32{ + "ERROR_REASON_UNSPECIFIED": 0, + "ADMIN_NOT_FOUND": 1, + "INVALID_CREDENTIALS": 2, + "INVALID_IDENTITY_TYPE": 3, + "UNAUTHENTICATED": 4, + "PERMISSION_DENIED": 5, + } +) + +func (x ErrorReason) Enum() *ErrorReason { + p := new(ErrorReason) + *p = x + return p +} + +func (x ErrorReason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ErrorReason) Descriptor() protoreflect.EnumDescriptor { + return file_kratos_admin_v1_error_reason_proto_enumTypes[0].Descriptor() +} + +func (ErrorReason) Type() protoreflect.EnumType { + return &file_kratos_admin_v1_error_reason_proto_enumTypes[0] +} + +func (x ErrorReason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ErrorReason.Descriptor instead. +func (ErrorReason) EnumDescriptor() ([]byte, []int) { + return file_kratos_admin_v1_error_reason_proto_rawDescGZIP(), []int{0} +} + +var File_kratos_admin_v1_error_reason_proto protoreflect.FileDescriptor + +const file_kratos_admin_v1_error_reason_proto_rawDesc = "" + + "\n" + + "\"kratos/admin/v1/error_reason.proto\x12\x0fkratos.admin.v1*\xa0\x01\n" + + "\vErrorReason\x12\x1c\n" + + "\x18ERROR_REASON_UNSPECIFIED\x10\x00\x12\x13\n" + + "\x0fADMIN_NOT_FOUND\x10\x01\x12\x17\n" + + "\x13INVALID_CREDENTIALS\x10\x02\x12\x19\n" + + "\x15INVALID_IDENTITY_TYPE\x10\x03\x12\x13\n" + + "\x0fUNAUTHENTICATED\x10\x04\x12\x15\n" + + "\x11PERMISSION_DENIED\x10\x05BQ\n" + + "\x13api.kratos.admin.v1P\x01Z8kra/api/kratos/admin/v1;v1b\x06proto3" + +var ( + file_kratos_admin_v1_error_reason_proto_rawDescOnce sync.Once + file_kratos_admin_v1_error_reason_proto_rawDescData []byte +) + +func file_kratos_admin_v1_error_reason_proto_rawDescGZIP() []byte { + file_kratos_admin_v1_error_reason_proto_rawDescOnce.Do(func() { + file_kratos_admin_v1_error_reason_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kratos_admin_v1_error_reason_proto_rawDesc), len(file_kratos_admin_v1_error_reason_proto_rawDesc))) + }) + return file_kratos_admin_v1_error_reason_proto_rawDescData +} + +var file_kratos_admin_v1_error_reason_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_kratos_admin_v1_error_reason_proto_goTypes = []any{ + (ErrorReason)(0), // 0: kratos.admin.v1.ErrorReason +} +var file_kratos_admin_v1_error_reason_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_kratos_admin_v1_error_reason_proto_init() } +func file_kratos_admin_v1_error_reason_proto_init() { + if File_kratos_admin_v1_error_reason_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kratos_admin_v1_error_reason_proto_rawDesc), len(file_kratos_admin_v1_error_reason_proto_rawDesc)), + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_kratos_admin_v1_error_reason_proto_goTypes, + DependencyIndexes: file_kratos_admin_v1_error_reason_proto_depIdxs, + EnumInfos: file_kratos_admin_v1_error_reason_proto_enumTypes, + }.Build() + File_kratos_admin_v1_error_reason_proto = out.File + file_kratos_admin_v1_error_reason_proto_goTypes = nil + file_kratos_admin_v1_error_reason_proto_depIdxs = nil +} diff --git a/api/kratos/admin/v1/error_reason.proto b/api/kratos/admin/v1/error_reason.proto new file mode 100644 index 0000000..9bed116 --- /dev/null +++ b/api/kratos/admin/v1/error_reason.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package kratos.admin.v1; + +option go_package = "kra/api/kratos/admin/v1;v1"; +option java_multiple_files = true; +option java_package = "api.kratos.admin.v1"; + +// ErrorReason is the stable vocabulary for the `reason` field of the error +// body. Values are wire-visible: protojson encodes them as their names, so +// clients branch on the name (e.g. "ADMIN_NOT_FOUND") rather than on an HTTP +// status. Surfaced in `biz` as `Err`. +enum ErrorReason { + // Unset. Never returned; reserved by proto3 for the zero value. + ERROR_REASON_UNSPECIFIED = 0; + // The requested admin does not exist. + ADMIN_NOT_FOUND = 1; + // Login failed. Returned for both an unknown account and a wrong password + // so callers cannot probe which accounts are registered. + INVALID_CREDENTIALS = 2; + // The login request carried no identity, or one this server does not accept. + INVALID_IDENTITY_TYPE = 3; + // The request carried no usable credential (missing or invalid token). + UNAUTHENTICATED = 4; + // The caller is authenticated but lacks the required access level. + PERMISSION_DENIED = 5; +} diff --git a/buf.gen.config.yaml b/buf.gen.config.yaml new file mode 100644 index 0000000..29ccaed --- /dev/null +++ b/buf.gen.config.yaml @@ -0,0 +1,7 @@ +version: v2 +inputs: + - directory: internal +plugins: + - local: ["go", "run", "google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11"] + out: internal + opt: paths=source_relative diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..7e92649 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,21 @@ +version: v2 +inputs: + - directory: api +plugins: + - local: ["go", "run", "google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11"] + out: api + opt: paths=source_relative + - local: ["go", "run", "google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.2"] + out: api + opt: paths=source_relative + - local: ["go", "run", "github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v3@v3.0.0-20260526000039-30da04b769dc"] + out: api + opt: paths=source_relative + - local: ["go", "run", "go.einride.tech/protoc-gen-typescript-http@v0.10.0"] + out: web/src/services + - local: ["go", "run", "github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.1"] + out: . + strategy: all + opt: + - fq_schema_naming=true + - default_response=false diff --git a/buf.lock b/buf.lock new file mode 100644 index 0000000..8447589 --- /dev/null +++ b/buf.lock @@ -0,0 +1,6 @@ +# Generated by buf. DO NOT EDIT. +version: v2 +deps: + - name: buf.build/googleapis/googleapis + commit: c17df5b2beca46928cc87d5656bd5343 + digest: b5:648a01e0170d4512dea7d564016165decd1ed6e34bef79fe54753e51ad7e27545709ad9157d7551270147d551155c595a2fb0bf5bb33b1c83040ddbce915c604 diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..38023b9 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,6 @@ +version: v2 +modules: + - path: api + - path: internal +deps: + - buf.build/googleapis/googleapis diff --git a/configs/config.yaml b/configs/config.yaml new file mode 100644 index 0000000..5e5453e --- /dev/null +++ b/configs/config.yaml @@ -0,0 +1,15 @@ +server: + http: + addr: 0.0.0.0:8000 + timeout: 1s + grpc: + addr: 0.0.0.0:9000 + timeout: 1s +data: + database: + driver: mysql + source: root:root@tcp(127.0.0.1:3306)/test?timeout=5s&parseTime=True&loc=Local&charset=utf8mb4 + redis: + addr: 127.0.0.1:6379 + read_timeout: 0.2s + write_timeout: 0.2s diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0fcb2fd --- /dev/null +++ b/go.mod @@ -0,0 +1,55 @@ +module kra + +go 1.25.7 + +require ( + entgo.io/ent v0.14.6 + github.com/go-kratos/aip-go/ents v0.0.0-20251213081434-74ffa1fc1588 + github.com/go-kratos/kratos/contrib/otel/v3 v3.0.0-20260617100506-4e232a3eff59 + github.com/go-kratos/kratos/v3 v3.0.0 + github.com/go-sql-driver/mysql v1.10.0 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/google/uuid v1.6.0 + github.com/google/wire v0.7.0 + go.einride.tech/aip v0.86.3 + go.uber.org/automaxprocs v1.6.0 + golang.org/x/crypto v0.53.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 + google.golang.org/grpc v1.81.1 + google.golang.org/protobuf v1.36.11 +) + +replace github.com/go-kratos/kratos/v3 v3.0.0 => github.com/go-kratos/kratos/v3 v3.0.0-20260617100506-4e232a3eff59 + +require ( + ariga.io/atlas v1.2.2 // indirect + filippo.io/edwards25519 v1.2.0 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/inflect v0.21.6 // indirect + github.com/go-playground/form/v4 v4.3.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/hcl/v2 v2.24.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/zclconf/go-cty v1.18.1 // indirect + github.com/zclconf/go-cty-yaml v1.2.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/tools v0.46.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0deb57d --- /dev/null +++ b/go.sum @@ -0,0 +1,139 @@ +ariga.io/atlas v1.2.2 h1:+cryCHGIVcbQxuK4rB7nK5pDkMC4g4XxqltYIioFWIQ= +ariga.io/atlas v1.2.2/go.mod h1:bEAo6Igo8PNJUyGOnni9jv+D83HyNEY3tNXV1ATtqI8= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +entgo.io/ent v0.14.6 h1:/f2696BpwuWAEEG6PVGWflg6+Inrpq4pRWuNlWz/Skk= +entgo.io/ent v0.14.6/go.mod h1:z46QBUdGC+BATwsedbDuREfSS0oSCV+csdEYlL4p73s= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +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/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +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/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-kratos/aip-go/ents v0.0.0-20251213081434-74ffa1fc1588 h1:e0dWyNWFeTgGCH7cRMROahTwMaQYtmHce/6fxVmA6yI= +github.com/go-kratos/aip-go/ents v0.0.0-20251213081434-74ffa1fc1588/go.mod h1:ifKMm4eJmaQz5WNKKhfClpCng8UxUB3sArof8wjwG78= +github.com/go-kratos/kratos/contrib/otel/v3 v3.0.0-20260617100506-4e232a3eff59 h1:FeDujUZF6a2pPS0RWGI52IXWPqVcGW1QP5vHDlAqLE8= +github.com/go-kratos/kratos/contrib/otel/v3 v3.0.0-20260617100506-4e232a3eff59/go.mod h1:hT2QNZ/0DPOvRqVqxoBBL/aMCOHhbbWh92mF87DBiCU= +github.com/go-kratos/kratos/v3 v3.0.0-20260617100506-4e232a3eff59 h1:32laNndL0GsYLuGwWquB6if8yrBQWEzXlkkXImcfeKA= +github.com/go-kratos/kratos/v3 v3.0.0-20260617100506-4e232a3eff59/go.mod h1:8l+0M5UPlm/5hWLvS+wzxHRVRv6sHMG6Lgb4+rw1KIs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/inflect v0.21.6 h1:0Se5BlyDT4hnV9JQQKA63W9TIUU6SC4hPJiY3qJOEzQ= +github.com/go-openapi/inflect v0.21.6/go.mod h1:ksYcnLD7j24H79hdqOMmWaLXjFXd0LTkRoBA0UazLW8= +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/form/v4 v4.3.0 h1:OVttojbQv2WNCs4P+VnjPtrt/+30Ipw4890W3OaFlvk= +github.com/go-playground/form/v4 v4.3.0/go.mod h1:Cpe1iYJKoXb1vILRXEwxpWMGWyQuqplQ/4cvPecy+Jo= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= +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.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= +github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM= +github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +github.com/zclconf/go-cty-yaml v1.2.0 h1:GDyL4+e/Qe/S0B7YaecMLbVvAR/Mp21CXMOSiCTOi1M= +github.com/zclconf/go-cty-yaml v1.2.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +go.einride.tech/aip v0.86.3 h1:jg80Ec4XBPYg1i7avzrl3MJol/dUwmMMLHtcmEMyxgM= +go.einride.tech/aip v0.86.3/go.mod h1:dZuN/0sXeoscfWqsW8QLcLrGZdvsCC1B2R2CZ4kHmao= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= +golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= +google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= +google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 h1:g0RAkxK/smSu/iRwC/KIX1mwUoVJtk2OjbgaeS4DmUM= +google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324/go.mod h1:Z4WJ5pJOYWFWcHEQUelD5QaZDknIQkpIL/+fyJOT9+A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 h1:9HZDLIdYBJXAnaFOr9WHrKVycfpY+75s9HGadC0305A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/gva b/gva new file mode 160000 index 0000000..02f3783 --- /dev/null +++ b/gva @@ -0,0 +1 @@ +Subproject commit 02f37833255e0e339c3d69199cb5a468f17de9fc diff --git a/internal/biz/README.md b/internal/biz/README.md new file mode 100644 index 0000000..26a66b6 --- /dev/null +++ b/internal/biz/README.md @@ -0,0 +1 @@ +# Biz diff --git a/internal/biz/admin.go b/internal/biz/admin.go new file mode 100644 index 0000000..435a2dc --- /dev/null +++ b/internal/biz/admin.go @@ -0,0 +1,136 @@ +package biz + +import ( + "context" + "time" + + "github.com/go-kratos/kratos/v3/errors" + "github.com/go-kratos/kratos/v3/log" + "golang.org/x/crypto/bcrypt" +) + +// Admin is a Admin model. +type Admin struct { + ID int64 + Name string + Email string + Password string + Access string + Avatar string + CreateTime time.Time + UpdateTime time.Time +} + +// AdminRepo is a Greater repo. +type AdminRepo interface { + FindByID(context.Context, int64) (*Admin, error) + FindByName(context.Context, string) (*Admin, error) + FindByEmail(context.Context, string) (*Admin, error) + ListAdmins(context.Context, ...ListOption) ([]*Admin, error) + CreateAdmin(context.Context, *Admin) (*Admin, error) + UpdateAdmin(context.Context, *Admin) (*Admin, error) + DeleteAdmin(context.Context, int64) error +} + +// AdminUsecase is a Admin usecase. +type AdminUsecase struct { + admin AdminRepo +} + +// NewAdminUsecase new a Admin usecase. +func NewAdminUsecase(repo AdminRepo) *AdminUsecase { + return &AdminUsecase{admin: repo} +} + +// LoginByUsername logs in a user by username and password. +func (uc *AdminUsecase) LoginByUsername(ctx context.Context, username, password string) (*Admin, error) { + user, err := uc.admin.FindByName(ctx, username) + if err != nil { + if errors.Is(err, ErrAdminNotFound) { + return nil, ErrInvalidCredentials + } + return nil, err + } + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + return nil, ErrInvalidCredentials + } + return user, nil +} + +// LoginByEmail logs in a user by email and password. +func (uc *AdminUsecase) LoginByEmail(ctx context.Context, email, password string) (*Admin, error) { + user, err := uc.admin.FindByEmail(ctx, email) + if err != nil { + if errors.Is(err, ErrAdminNotFound) { + return nil, ErrInvalidCredentials + } + return nil, err + } + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + return nil, ErrInvalidCredentials + } + return user, nil +} + +// Logout logs out the current user. +func (uc *AdminUsecase) Logout(ctx context.Context, adminID int64) error { + admin, err := uc.admin.FindByID(ctx, adminID) + if err != nil { + return err + } + log.InfoContext(ctx, "admin logged out", "name", admin.Name) + return nil +} + +// Current returns the current logged in user. +func (uc *AdminUsecase) GetAdmin(ctx context.Context, id int64) (*Admin, error) { + return uc.admin.FindByID(ctx, id) +} + +// ListAdmins lists admin users with pagination. +func (uc *AdminUsecase) ListAdmins(ctx context.Context, opts ...ListOption) ([]*Admin, error) { + admins, err := uc.admin.ListAdmins(ctx, opts...) + if err != nil { + return nil, err + } + return admins, nil +} + +// CreateAdmin creates a new admin user. +func (uc *AdminUsecase) CreateAdmin(ctx context.Context, admin *Admin) (*Admin, error) { + if admin.Password != "" { + hashed, err := hashPassword(admin.Password) + if err != nil { + return nil, err + } + admin.Password = hashed + } + return uc.admin.CreateAdmin(ctx, admin) +} + +// UpdateAdmin updates an existing admin user. +func (uc *AdminUsecase) UpdateAdmin(ctx context.Context, admin *Admin) (*Admin, error) { + // Empty password means "leave unchanged"; only hash when a new one is set. + if admin.Password != "" { + hashed, err := hashPassword(admin.Password) + if err != nil { + return nil, err + } + admin.Password = hashed + } + return uc.admin.UpdateAdmin(ctx, admin) +} + +// hashPassword hashes a plaintext password using bcrypt. +func hashPassword(password string) (string, error) { + hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashed), nil +} + +// DeleteAdmin deletes an admin user by ID. +func (uc *AdminUsecase) DeleteAdmin(ctx context.Context, id int64) error { + return uc.admin.DeleteAdmin(ctx, id) +} diff --git a/internal/biz/admin_test.go b/internal/biz/admin_test.go new file mode 100644 index 0000000..7d87f7f --- /dev/null +++ b/internal/biz/admin_test.go @@ -0,0 +1,166 @@ +package biz + +import ( + "context" + "testing" + + "github.com/go-kratos/kratos/v3/errors" + "golang.org/x/crypto/bcrypt" +) + +// fakeAdminRepo is an in-memory AdminRepo for testing the usecase in +// isolation from the data layer. +type fakeAdminRepo struct { + byName map[string]*Admin + byEmail map[string]*Admin + created []*Admin +} + +func newFakeAdminRepo() *fakeAdminRepo { + return &fakeAdminRepo{ + byName: make(map[string]*Admin), + byEmail: make(map[string]*Admin), + } +} + +func (r *fakeAdminRepo) FindByID(context.Context, int64) (*Admin, error) { + return nil, ErrAdminNotFound +} + +func (r *fakeAdminRepo) FindByName(_ context.Context, name string) (*Admin, error) { + a, ok := r.byName[name] + if !ok { + return nil, ErrAdminNotFound + } + return a, nil +} + +func (r *fakeAdminRepo) FindByEmail(_ context.Context, email string) (*Admin, error) { + a, ok := r.byEmail[email] + if !ok { + return nil, ErrAdminNotFound + } + return a, nil +} + +func (r *fakeAdminRepo) ListAdmins(context.Context, ...ListOption) ([]*Admin, error) { + return nil, nil +} + +func (r *fakeAdminRepo) CreateAdmin(_ context.Context, a *Admin) (*Admin, error) { + r.created = append(r.created, a) + return a, nil +} + +func (r *fakeAdminRepo) UpdateAdmin(_ context.Context, a *Admin) (*Admin, error) { + return a, nil +} + +func (r *fakeAdminRepo) DeleteAdmin(context.Context, int64) error { + return nil +} + +// seedAdmin inserts an admin with a bcrypt-hashed password. +func seedAdmin(t *testing.T, repo *fakeAdminRepo, name, email, password string) *Admin { + t.Helper() + hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + t.Fatalf("hash password: %v", err) + } + a := &Admin{ID: 1, Name: name, Email: email, Password: string(hashed)} + repo.byName[name] = a + repo.byEmail[email] = a + return a +} + +func TestLoginByUsername_Success(t *testing.T) { + repo := newFakeAdminRepo() + seedAdmin(t, repo, "admin", "admin@example.com", "admin") + uc := NewAdminUsecase(repo) + + got, err := uc.LoginByUsername(context.Background(), "admin", "admin") + if err != nil { + t.Fatalf("expected login success, got error: %v", err) + } + if got.Name != "admin" { + t.Fatalf("expected admin user, got %q", got.Name) + } +} + +func TestLoginByEmail_Success(t *testing.T) { + repo := newFakeAdminRepo() + seedAdmin(t, repo, "admin", "admin@example.com", "admin") + uc := NewAdminUsecase(repo) + + got, err := uc.LoginByEmail(context.Background(), "admin@example.com", "admin") + if err != nil { + t.Fatalf("expected login success, got error: %v", err) + } + if got.Email != "admin@example.com" { + t.Fatalf("expected admin user, got %q", got.Email) + } +} + +func TestLoginByUsername_WrongPassword(t *testing.T) { + repo := newFakeAdminRepo() + seedAdmin(t, repo, "admin", "admin@example.com", "admin") + uc := NewAdminUsecase(repo) + + _, err := uc.LoginByUsername(context.Background(), "admin", "wrong") + if err == nil { + t.Fatal("expected error for wrong password, got nil") + } + if errors.Code(err) != 401 { + t.Fatalf("expected 401 unauthorized, got code %d (%v)", errors.Code(err), err) + } +} + +func TestLoginByUsername_UserNotFound(t *testing.T) { + repo := newFakeAdminRepo() + uc := NewAdminUsecase(repo) + + _, err := uc.LoginByUsername(context.Background(), "ghost", "admin") + if err == nil { + t.Fatal("expected error for unknown user, got nil") + } + // Must be the same opaque 401 as a wrong password, to avoid user enumeration. + if errors.Code(err) != 401 { + t.Fatalf("expected 401 unauthorized, got code %d (%v)", errors.Code(err), err) + } + if errors.Is(err, ErrAdminNotFound) { + t.Fatal("login must not leak ErrAdminNotFound for unknown users") + } +} + +func TestCreateAdmin_HashesPassword(t *testing.T) { + repo := newFakeAdminRepo() + uc := NewAdminUsecase(repo) + + _, err := uc.CreateAdmin(context.Background(), &Admin{Name: "bob", Password: "secret"}) + if err != nil { + t.Fatalf("create admin: %v", err) + } + if len(repo.created) != 1 { + t.Fatalf("expected 1 created admin, got %d", len(repo.created)) + } + stored := repo.created[0].Password + if stored == "secret" { + t.Fatal("password was stored in plaintext") + } + if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte("secret")); err != nil { + t.Fatalf("stored password is not a valid bcrypt hash of the input: %v", err) + } +} + +func TestUpdateAdmin_EmptyPasswordLeftUnchanged(t *testing.T) { + repo := newFakeAdminRepo() + uc := NewAdminUsecase(repo) + + got, err := uc.UpdateAdmin(context.Background(), &Admin{ID: 1, Name: "bob", Password: ""}) + if err != nil { + t.Fatalf("update admin: %v", err) + } + if got.Password != "" { + t.Fatalf("empty password must be passed through unchanged, got %q", got.Password) + } +} diff --git a/internal/biz/biz.go b/internal/biz/biz.go new file mode 100644 index 0000000..9eb395f --- /dev/null +++ b/internal/biz/biz.go @@ -0,0 +1,6 @@ +package biz + +import "github.com/google/wire" + +// ProviderSet is biz providers. +var ProviderSet = wire.NewSet(NewAdminUsecase) diff --git a/internal/biz/errors.go b/internal/biz/errors.go new file mode 100644 index 0000000..e87343e --- /dev/null +++ b/internal/biz/errors.go @@ -0,0 +1,14 @@ +package biz + +import ( + v1 "kra/api/kratos/admin/v1" + "github.com/go-kratos/kratos/v3/errors" +) + +var ( + // ErrAdminNotFound error admin not found. + ErrAdminNotFound = errors.NotFound(v1.ErrorReason_ADMIN_NOT_FOUND.String(), "admin not found") + // ErrInvalidCredentials is returned for any failed login, regardless of + // whether the user exists, to avoid leaking which accounts are registered. + ErrInvalidCredentials = errors.Unauthorized(v1.ErrorReason_INVALID_CREDENTIALS.String(), "invalid credentials") +) diff --git a/internal/biz/pagination.go b/internal/biz/pagination.go new file mode 100644 index 0000000..27bdbc4 --- /dev/null +++ b/internal/biz/pagination.go @@ -0,0 +1,39 @@ +package biz + +import ( + "go.einride.tech/aip/filtering" + "go.einride.tech/aip/ordering" +) + +type ListOption func(*ListOptions) + +type ListOptions struct { + Filter filtering.Filter + OrderBy ordering.OrderBy + Offset int + Limit int +} + +func ListFilter(filter filtering.Filter) ListOption { + return func(o *ListOptions) { + o.Filter = filter + } +} + +func ListOrderBy(orderBy ordering.OrderBy) ListOption { + return func(o *ListOptions) { + o.OrderBy = orderBy + } +} + +func ListOffset(offset int) ListOption { + return func(o *ListOptions) { + o.Offset = offset + } +} + +func ListLimit(limit int) ListOption { + return func(o *ListOptions) { + o.Limit = limit + } +} diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go new file mode 100644 index 0000000..621e117 --- /dev/null +++ b/internal/conf/conf.pb.go @@ -0,0 +1,516 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: conf/conf.proto + +package conf + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Bootstrap struct { + state protoimpl.MessageState `protogen:"open.v1"` + Server *Server `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + Data *Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Bootstrap) Reset() { + *x = Bootstrap{} + mi := &file_conf_conf_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Bootstrap) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Bootstrap) ProtoMessage() {} + +func (x *Bootstrap) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Bootstrap.ProtoReflect.Descriptor instead. +func (*Bootstrap) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{0} +} + +func (x *Bootstrap) GetServer() *Server { + if x != nil { + return x.Server + } + return nil +} + +func (x *Bootstrap) GetData() *Data { + if x != nil { + return x.Data + } + return nil +} + +type Server struct { + state protoimpl.MessageState `protogen:"open.v1"` + Http *Server_HTTP `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"` + Grpc *Server_GRPC `protobuf:"bytes,2,opt,name=grpc,proto3" json:"grpc,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Server) Reset() { + *x = Server{} + mi := &file_conf_conf_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Server) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Server) ProtoMessage() {} + +func (x *Server) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Server.ProtoReflect.Descriptor instead. +func (*Server) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{1} +} + +func (x *Server) GetHttp() *Server_HTTP { + if x != nil { + return x.Http + } + return nil +} + +func (x *Server) GetGrpc() *Server_GRPC { + if x != nil { + return x.Grpc + } + return nil +} + +type Data struct { + state protoimpl.MessageState `protogen:"open.v1"` + Database *Data_Database `protobuf:"bytes,1,opt,name=database,proto3" json:"database,omitempty"` + Redis *Data_Redis `protobuf:"bytes,2,opt,name=redis,proto3" json:"redis,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Data) Reset() { + *x = Data{} + mi := &file_conf_conf_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Data) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Data) ProtoMessage() {} + +func (x *Data) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Data.ProtoReflect.Descriptor instead. +func (*Data) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{2} +} + +func (x *Data) GetDatabase() *Data_Database { + if x != nil { + return x.Database + } + return nil +} + +func (x *Data) GetRedis() *Data_Redis { + if x != nil { + return x.Redis + } + return nil +} + +type Server_HTTP struct { + state protoimpl.MessageState `protogen:"open.v1"` + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,3,opt,name=timeout,proto3" json:"timeout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Server_HTTP) Reset() { + *x = Server_HTTP{} + mi := &file_conf_conf_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Server_HTTP) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Server_HTTP) ProtoMessage() {} + +func (x *Server_HTTP) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Server_HTTP.ProtoReflect.Descriptor instead. +func (*Server_HTTP) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *Server_HTTP) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Server_HTTP) GetAddr() string { + if x != nil { + return x.Addr + } + return "" +} + +func (x *Server_HTTP) GetTimeout() *durationpb.Duration { + if x != nil { + return x.Timeout + } + return nil +} + +type Server_GRPC struct { + state protoimpl.MessageState `protogen:"open.v1"` + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,3,opt,name=timeout,proto3" json:"timeout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Server_GRPC) Reset() { + *x = Server_GRPC{} + mi := &file_conf_conf_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Server_GRPC) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Server_GRPC) ProtoMessage() {} + +func (x *Server_GRPC) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Server_GRPC.ProtoReflect.Descriptor instead. +func (*Server_GRPC) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{1, 1} +} + +func (x *Server_GRPC) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Server_GRPC) GetAddr() string { + if x != nil { + return x.Addr + } + return "" +} + +func (x *Server_GRPC) GetTimeout() *durationpb.Duration { + if x != nil { + return x.Timeout + } + return nil +} + +type Data_Database struct { + state protoimpl.MessageState `protogen:"open.v1"` + Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"` + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Data_Database) Reset() { + *x = Data_Database{} + mi := &file_conf_conf_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Data_Database) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Data_Database) ProtoMessage() {} + +func (x *Data_Database) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Data_Database.ProtoReflect.Descriptor instead. +func (*Data_Database) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *Data_Database) GetDriver() string { + if x != nil { + return x.Driver + } + return "" +} + +func (x *Data_Database) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +type Data_Redis struct { + state protoimpl.MessageState `protogen:"open.v1"` + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` + ReadTimeout *durationpb.Duration `protobuf:"bytes,3,opt,name=read_timeout,json=readTimeout,proto3" json:"read_timeout,omitempty"` + WriteTimeout *durationpb.Duration `protobuf:"bytes,4,opt,name=write_timeout,json=writeTimeout,proto3" json:"write_timeout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Data_Redis) Reset() { + *x = Data_Redis{} + mi := &file_conf_conf_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Data_Redis) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Data_Redis) ProtoMessage() {} + +func (x *Data_Redis) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Data_Redis.ProtoReflect.Descriptor instead. +func (*Data_Redis) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{2, 1} +} + +func (x *Data_Redis) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Data_Redis) GetAddr() string { + if x != nil { + return x.Addr + } + return "" +} + +func (x *Data_Redis) GetReadTimeout() *durationpb.Duration { + if x != nil { + return x.ReadTimeout + } + return nil +} + +func (x *Data_Redis) GetWriteTimeout() *durationpb.Duration { + if x != nil { + return x.WriteTimeout + } + return nil +} + +var File_conf_conf_proto protoreflect.FileDescriptor + +const file_conf_conf_proto_rawDesc = "" + + "\n" + + "\x0fconf/conf.proto\x12\n" + + "kratos.api\x1a\x1egoogle/protobuf/duration.proto\"]\n" + + "\tBootstrap\x12*\n" + + "\x06server\x18\x01 \x01(\v2\x12.kratos.api.ServerR\x06server\x12$\n" + + "\x04data\x18\x02 \x01(\v2\x10.kratos.api.DataR\x04data\"\xb8\x02\n" + + "\x06Server\x12+\n" + + "\x04http\x18\x01 \x01(\v2\x17.kratos.api.Server.HTTPR\x04http\x12+\n" + + "\x04grpc\x18\x02 \x01(\v2\x17.kratos.api.Server.GRPCR\x04grpc\x1ai\n" + + "\x04HTTP\x12\x18\n" + + "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + + "\x04addr\x18\x02 \x01(\tR\x04addr\x123\n" + + "\atimeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\atimeout\x1ai\n" + + "\x04GRPC\x12\x18\n" + + "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + + "\x04addr\x18\x02 \x01(\tR\x04addr\x123\n" + + "\atimeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\atimeout\"\xdd\x02\n" + + "\x04Data\x125\n" + + "\bdatabase\x18\x01 \x01(\v2\x19.kratos.api.Data.DatabaseR\bdatabase\x12,\n" + + "\x05redis\x18\x02 \x01(\v2\x16.kratos.api.Data.RedisR\x05redis\x1a:\n" + + "\bDatabase\x12\x16\n" + + "\x06driver\x18\x01 \x01(\tR\x06driver\x12\x16\n" + + "\x06source\x18\x02 \x01(\tR\x06source\x1a\xb3\x01\n" + + "\x05Redis\x12\x18\n" + + "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + + "\x04addr\x18\x02 \x01(\tR\x04addr\x12<\n" + + "\fread_timeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\vreadTimeout\x12>\n" + + "\rwrite_timeout\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\fwriteTimeoutB6Z4kra/internal/conf;confb\x06proto3" + +var ( + file_conf_conf_proto_rawDescOnce sync.Once + file_conf_conf_proto_rawDescData []byte +) + +func file_conf_conf_proto_rawDescGZIP() []byte { + file_conf_conf_proto_rawDescOnce.Do(func() { + file_conf_conf_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_conf_conf_proto_rawDesc), len(file_conf_conf_proto_rawDesc))) + }) + return file_conf_conf_proto_rawDescData +} + +var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_conf_conf_proto_goTypes = []any{ + (*Bootstrap)(nil), // 0: kratos.api.Bootstrap + (*Server)(nil), // 1: kratos.api.Server + (*Data)(nil), // 2: kratos.api.Data + (*Server_HTTP)(nil), // 3: kratos.api.Server.HTTP + (*Server_GRPC)(nil), // 4: kratos.api.Server.GRPC + (*Data_Database)(nil), // 5: kratos.api.Data.Database + (*Data_Redis)(nil), // 6: kratos.api.Data.Redis + (*durationpb.Duration)(nil), // 7: google.protobuf.Duration +} +var file_conf_conf_proto_depIdxs = []int32{ + 1, // 0: kratos.api.Bootstrap.server:type_name -> kratos.api.Server + 2, // 1: kratos.api.Bootstrap.data:type_name -> kratos.api.Data + 3, // 2: kratos.api.Server.http:type_name -> kratos.api.Server.HTTP + 4, // 3: kratos.api.Server.grpc:type_name -> kratos.api.Server.GRPC + 5, // 4: kratos.api.Data.database:type_name -> kratos.api.Data.Database + 6, // 5: kratos.api.Data.redis:type_name -> kratos.api.Data.Redis + 7, // 6: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration + 7, // 7: kratos.api.Server.GRPC.timeout:type_name -> google.protobuf.Duration + 7, // 8: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration + 7, // 9: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration + 10, // [10:10] is the sub-list for method output_type + 10, // [10:10] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_conf_conf_proto_init() } +func file_conf_conf_proto_init() { + if File_conf_conf_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_conf_conf_proto_rawDesc), len(file_conf_conf_proto_rawDesc)), + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_conf_conf_proto_goTypes, + DependencyIndexes: file_conf_conf_proto_depIdxs, + MessageInfos: file_conf_conf_proto_msgTypes, + }.Build() + File_conf_conf_proto = out.File + file_conf_conf_proto_goTypes = nil + file_conf_conf_proto_depIdxs = nil +} diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto new file mode 100644 index 0000000..44e0fed --- /dev/null +++ b/internal/conf/conf.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package kratos.api; + +option go_package = "kra/internal/conf;conf"; + +import "google/protobuf/duration.proto"; + +message Bootstrap { + Server server = 1; + Data data = 2; +} + +message Server { + message HTTP { + string network = 1; + string addr = 2; + google.protobuf.Duration timeout = 3; + } + message GRPC { + string network = 1; + string addr = 2; + google.protobuf.Duration timeout = 3; + } + HTTP http = 1; + GRPC grpc = 2; +} + +message Data { + message Database { + string driver = 1; + string source = 2; + } + message Redis { + string network = 1; + string addr = 2; + google.protobuf.Duration read_timeout = 3; + google.protobuf.Duration write_timeout = 4; + } + Database database = 1; + Redis redis = 2; +} diff --git a/internal/data/README.md b/internal/data/README.md new file mode 100644 index 0000000..599c41b --- /dev/null +++ b/internal/data/README.md @@ -0,0 +1 @@ +# Data diff --git a/internal/data/admin.go b/internal/data/admin.go new file mode 100644 index 0000000..b555f85 --- /dev/null +++ b/internal/data/admin.go @@ -0,0 +1,127 @@ +package data + +import ( + "context" + "time" + + "github.com/go-kratos/aip-go/ents" + "kra/internal/biz" + "kra/internal/data/ent" + "kra/internal/data/ent/admin" +) + +func convertAdmin(po *ent.Admin) *biz.Admin { + return &biz.Admin{ + ID: po.ID, + Name: po.Name, + Email: po.Email, + Avatar: po.Avatar, + Access: po.Access, + Password: po.Password, + CreateTime: po.CreateTime, + UpdateTime: po.UpdateTime, + } +} + +type adminRepo struct { + data *Data +} + +// NewAdminRepo creates a new AdminRepo instance. +func NewAdminRepo(data *Data) biz.AdminRepo { + return &adminRepo{ + data: data, + } +} + +func (r *adminRepo) FindByID(ctx context.Context, id int64) (*biz.Admin, error) { + po, err := r.data.db.Admin.Get(ctx, id) + if err != nil { + if ent.IsNotFound(err) { + return nil, biz.ErrAdminNotFound + } + return nil, err + } + return convertAdmin(po), nil +} + +func (r *adminRepo) FindByName(ctx context.Context, name string) (*biz.Admin, error) { + po, err := r.data.db.Admin.Query().Where(admin.NameEQ(name)).Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + return nil, biz.ErrAdminNotFound + } + return nil, err + } + return convertAdmin(po), nil +} + +func (r *adminRepo) FindByEmail(ctx context.Context, email string) (*biz.Admin, error) { + po, err := r.data.db.Admin.Query().Where(admin.EmailEQ(email)).Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + return nil, biz.ErrAdminNotFound + } + return nil, err + } + return convertAdmin(po), nil +} + +func (r *adminRepo) ListAdmins(ctx context.Context, opts ...biz.ListOption) ([]*biz.Admin, error) { + o := biz.ListOptions{Limit: 20} + for _, opt := range opts { + opt(&o) + } + pos, err := r.data.db.Admin.Query(). + Where(ents.ApplyFilter(o.Filter)). + Order(ents.ApplyOrderBy(o.OrderBy)). + Offset(o.Offset). + Limit(o.Limit). + All(ctx) + if err != nil { + return nil, err + } + var admins []*biz.Admin + for _, po := range pos { + admins = append(admins, convertAdmin(po)) + } + return admins, nil +} + +func (r *adminRepo) CreateAdmin(ctx context.Context, admin *biz.Admin) (*biz.Admin, error) { + po, err := r.data.db.Admin.Create(). + SetName(admin.Name). + SetEmail(admin.Email). + SetAvatar(admin.Avatar). + SetAccess(admin.Access). + SetPassword(admin.Password). + SetCreateTime(time.Now()). + SetUpdateTime(time.Now()). + Save(ctx) + if err != nil { + return nil, err + } + return convertAdmin(po), nil +} + +func (r *adminRepo) UpdateAdmin(ctx context.Context, admin *biz.Admin) (*biz.Admin, error) { + update := r.data.db.Admin.UpdateOneID(admin.ID). + SetName(admin.Name). + SetEmail(admin.Email). + SetAccess(admin.Access). + SetAvatar(admin.Avatar). + SetUpdateTime(time.Now()) + // Only update the password if it's not empty + if admin.Password != "" { + update.SetPassword(admin.Password) + } + po, err := update.Save(ctx) + if err != nil { + return nil, err + } + return convertAdmin(po), nil +} + +func (r *adminRepo) DeleteAdmin(ctx context.Context, id int64) error { + return r.data.db.Admin.DeleteOneID(id).Exec(ctx) +} diff --git a/internal/data/data.go b/internal/data/data.go new file mode 100644 index 0000000..4c037a5 --- /dev/null +++ b/internal/data/data.go @@ -0,0 +1,43 @@ +package data + +import ( + "context" + "log" + "os" + + "kra/internal/conf" + "kra/internal/data/ent" + "kra/internal/data/ent/migrate" + _ "github.com/go-sql-driver/mysql" + "github.com/google/wire" +) + +// ProviderSet is data providers. +var ProviderSet = wire.NewSet(NewData, NewAdminRepo) + +// Data is a struct that contains the database client. +type Data struct { + db *ent.Client +} + +// NewData creates a new Data instance. +func NewData(c *conf.Data) (*Data, func(), error) { + db, err := ent.Open(c.Database.Driver, c.Database.Source) + if err != nil { + log.Fatalf("failed opening connection to database: %v", err) + } + if os.Getenv("DEPLOY_ENV") == "dev" { + // Enable debug mode for detailed logging. + db = db.Debug() + // Run the auto migration tool. + if err = db.Schema.Create(context.Background(), migrate.WithDropIndex(true)); err != nil { + return nil, nil, err + } + } + cleanup := func() { + db.Close() + } + return &Data{ + db: db, + }, cleanup, nil +} diff --git a/internal/data/ent/admin.go b/internal/data/ent/admin.go new file mode 100644 index 0000000..3a98dc3 --- /dev/null +++ b/internal/data/ent/admin.go @@ -0,0 +1,172 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "kra/internal/data/ent/admin" +) + +// Admin is the model entity for the Admin schema. +type Admin struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Email holds the value of the "email" field. + Email string `json:"email,omitempty"` + // Avatar holds the value of the "avatar" field. + Avatar string `json:"avatar,omitempty"` + // Access holds the value of the "access" field. + Access string `json:"access,omitempty"` + // Password holds the value of the "password" field. + Password string `json:"password,omitempty"` + // CreateTime holds the value of the "create_time" field. + CreateTime time.Time `json:"create_time,omitempty"` + // UpdateTime holds the value of the "update_time" field. + UpdateTime time.Time `json:"update_time,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Admin) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case admin.FieldID: + values[i] = new(sql.NullInt64) + case admin.FieldName, admin.FieldEmail, admin.FieldAvatar, admin.FieldAccess, admin.FieldPassword: + values[i] = new(sql.NullString) + case admin.FieldCreateTime, admin.FieldUpdateTime: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Admin fields. +func (_m *Admin) 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 admin.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + _m.ID = int64(value.Int64) + case admin.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case admin.FieldEmail: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field email", values[i]) + } else if value.Valid { + _m.Email = value.String + } + case admin.FieldAvatar: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field avatar", values[i]) + } else if value.Valid { + _m.Avatar = value.String + } + case admin.FieldAccess: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field access", values[i]) + } else if value.Valid { + _m.Access = value.String + } + case admin.FieldPassword: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field password", values[i]) + } else if value.Valid { + _m.Password = value.String + } + case admin.FieldCreateTime: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field create_time", values[i]) + } else if value.Valid { + _m.CreateTime = value.Time + } + case admin.FieldUpdateTime: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field update_time", values[i]) + } else if value.Valid { + _m.UpdateTime = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Admin. +// This includes values selected through modifiers, order, etc. +func (_m *Admin) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this Admin. +// Note that you need to call Admin.Unwrap() before calling this method if this Admin +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *Admin) Update() *AdminUpdateOne { + return NewAdminClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the Admin 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 (_m *Admin) Unwrap() *Admin { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: Admin is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *Admin) String() string { + var builder strings.Builder + builder.WriteString("Admin(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + builder.WriteString("email=") + builder.WriteString(_m.Email) + builder.WriteString(", ") + builder.WriteString("avatar=") + builder.WriteString(_m.Avatar) + builder.WriteString(", ") + builder.WriteString("access=") + builder.WriteString(_m.Access) + builder.WriteString(", ") + builder.WriteString("password=") + builder.WriteString(_m.Password) + builder.WriteString(", ") + builder.WriteString("create_time=") + builder.WriteString(_m.CreateTime.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("update_time=") + builder.WriteString(_m.UpdateTime.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// Admins is a parsable slice of Admin. +type Admins []*Admin diff --git a/internal/data/ent/admin/admin.go b/internal/data/ent/admin/admin.go new file mode 100644 index 0000000..767c0ee --- /dev/null +++ b/internal/data/ent/admin/admin.go @@ -0,0 +1,116 @@ +// Code generated by ent, DO NOT EDIT. + +package admin + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the admin type in the database. + Label = "admin" + // 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" + // FieldEmail holds the string denoting the email field in the database. + FieldEmail = "email" + // FieldAvatar holds the string denoting the avatar field in the database. + FieldAvatar = "avatar" + // FieldAccess holds the string denoting the access field in the database. + FieldAccess = "access" + // FieldPassword holds the string denoting the password field in the database. + FieldPassword = "password" + // FieldCreateTime holds the string denoting the create_time field in the database. + FieldCreateTime = "create_time" + // FieldUpdateTime holds the string denoting the update_time field in the database. + FieldUpdateTime = "update_time" + // Table holds the table name of the admin in the database. + Table = "admins" +) + +// Columns holds all SQL columns for admin fields. +var Columns = []string{ + FieldID, + FieldName, + FieldEmail, + FieldAvatar, + FieldAccess, + FieldPassword, + FieldCreateTime, + FieldUpdateTime, +} + +// 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 +} + +var ( + // DefaultName holds the default value on creation for the "name" field. + DefaultName string + // DefaultEmail holds the default value on creation for the "email" field. + DefaultEmail string + // DefaultAvatar holds the default value on creation for the "avatar" field. + DefaultAvatar string + // DefaultAccess holds the default value on creation for the "access" field. + DefaultAccess string + // DefaultPassword holds the default value on creation for the "password" field. + DefaultPassword string + // DefaultCreateTime holds the default value on creation for the "create_time" field. + DefaultCreateTime func() time.Time + // DefaultUpdateTime holds the default value on creation for the "update_time" field. + DefaultUpdateTime func() time.Time + // UpdateDefaultUpdateTime holds the default value on update for the "update_time" field. + UpdateDefaultUpdateTime func() time.Time +) + +// OrderOption defines the ordering options for the Admin 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() +} + +// ByEmail orders the results by the email field. +func ByEmail(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldEmail, opts...).ToFunc() +} + +// ByAvatar orders the results by the avatar field. +func ByAvatar(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAvatar, opts...).ToFunc() +} + +// ByAccess orders the results by the access field. +func ByAccess(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAccess, opts...).ToFunc() +} + +// ByPassword orders the results by the password field. +func ByPassword(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPassword, opts...).ToFunc() +} + +// ByCreateTime orders the results by the create_time field. +func ByCreateTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreateTime, opts...).ToFunc() +} + +// ByUpdateTime orders the results by the update_time field. +func ByUpdateTime(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdateTime, opts...).ToFunc() +} diff --git a/internal/data/ent/admin/where.go b/internal/data/ent/admin/where.go new file mode 100644 index 0000000..42c98e2 --- /dev/null +++ b/internal/data/ent/admin/where.go @@ -0,0 +1,510 @@ +// Code generated by ent, DO NOT EDIT. + +package admin + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "kra/internal/data/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldID, id)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldName, v)) +} + +// Email applies equality check predicate on the "email" field. It's identical to EmailEQ. +func Email(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldEmail, v)) +} + +// Avatar applies equality check predicate on the "avatar" field. It's identical to AvatarEQ. +func Avatar(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldAvatar, v)) +} + +// Access applies equality check predicate on the "access" field. It's identical to AccessEQ. +func Access(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldAccess, v)) +} + +// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ. +func Password(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldPassword, v)) +} + +// CreateTime applies equality check predicate on the "create_time" field. It's identical to CreateTimeEQ. +func CreateTime(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldCreateTime, v)) +} + +// UpdateTime applies equality check predicate on the "update_time" field. It's identical to UpdateTimeEQ. +func UpdateTime(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldUpdateTime, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.Admin { + return predicate.Admin(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldContainsFold(FieldName, v)) +} + +// EmailEQ applies the EQ predicate on the "email" field. +func EmailEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldEmail, v)) +} + +// EmailNEQ applies the NEQ predicate on the "email" field. +func EmailNEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldEmail, v)) +} + +// EmailIn applies the In predicate on the "email" field. +func EmailIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldEmail, vs...)) +} + +// EmailNotIn applies the NotIn predicate on the "email" field. +func EmailNotIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldEmail, vs...)) +} + +// EmailGT applies the GT predicate on the "email" field. +func EmailGT(v string) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldEmail, v)) +} + +// EmailGTE applies the GTE predicate on the "email" field. +func EmailGTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldEmail, v)) +} + +// EmailLT applies the LT predicate on the "email" field. +func EmailLT(v string) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldEmail, v)) +} + +// EmailLTE applies the LTE predicate on the "email" field. +func EmailLTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldEmail, v)) +} + +// EmailContains applies the Contains predicate on the "email" field. +func EmailContains(v string) predicate.Admin { + return predicate.Admin(sql.FieldContains(FieldEmail, v)) +} + +// EmailHasPrefix applies the HasPrefix predicate on the "email" field. +func EmailHasPrefix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasPrefix(FieldEmail, v)) +} + +// EmailHasSuffix applies the HasSuffix predicate on the "email" field. +func EmailHasSuffix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasSuffix(FieldEmail, v)) +} + +// EmailEqualFold applies the EqualFold predicate on the "email" field. +func EmailEqualFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldEqualFold(FieldEmail, v)) +} + +// EmailContainsFold applies the ContainsFold predicate on the "email" field. +func EmailContainsFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldContainsFold(FieldEmail, v)) +} + +// AvatarEQ applies the EQ predicate on the "avatar" field. +func AvatarEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldAvatar, v)) +} + +// AvatarNEQ applies the NEQ predicate on the "avatar" field. +func AvatarNEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldAvatar, v)) +} + +// AvatarIn applies the In predicate on the "avatar" field. +func AvatarIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldAvatar, vs...)) +} + +// AvatarNotIn applies the NotIn predicate on the "avatar" field. +func AvatarNotIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldAvatar, vs...)) +} + +// AvatarGT applies the GT predicate on the "avatar" field. +func AvatarGT(v string) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldAvatar, v)) +} + +// AvatarGTE applies the GTE predicate on the "avatar" field. +func AvatarGTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldAvatar, v)) +} + +// AvatarLT applies the LT predicate on the "avatar" field. +func AvatarLT(v string) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldAvatar, v)) +} + +// AvatarLTE applies the LTE predicate on the "avatar" field. +func AvatarLTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldAvatar, v)) +} + +// AvatarContains applies the Contains predicate on the "avatar" field. +func AvatarContains(v string) predicate.Admin { + return predicate.Admin(sql.FieldContains(FieldAvatar, v)) +} + +// AvatarHasPrefix applies the HasPrefix predicate on the "avatar" field. +func AvatarHasPrefix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasPrefix(FieldAvatar, v)) +} + +// AvatarHasSuffix applies the HasSuffix predicate on the "avatar" field. +func AvatarHasSuffix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasSuffix(FieldAvatar, v)) +} + +// AvatarEqualFold applies the EqualFold predicate on the "avatar" field. +func AvatarEqualFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldEqualFold(FieldAvatar, v)) +} + +// AvatarContainsFold applies the ContainsFold predicate on the "avatar" field. +func AvatarContainsFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldContainsFold(FieldAvatar, v)) +} + +// AccessEQ applies the EQ predicate on the "access" field. +func AccessEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldAccess, v)) +} + +// AccessNEQ applies the NEQ predicate on the "access" field. +func AccessNEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldAccess, v)) +} + +// AccessIn applies the In predicate on the "access" field. +func AccessIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldAccess, vs...)) +} + +// AccessNotIn applies the NotIn predicate on the "access" field. +func AccessNotIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldAccess, vs...)) +} + +// AccessGT applies the GT predicate on the "access" field. +func AccessGT(v string) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldAccess, v)) +} + +// AccessGTE applies the GTE predicate on the "access" field. +func AccessGTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldAccess, v)) +} + +// AccessLT applies the LT predicate on the "access" field. +func AccessLT(v string) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldAccess, v)) +} + +// AccessLTE applies the LTE predicate on the "access" field. +func AccessLTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldAccess, v)) +} + +// AccessContains applies the Contains predicate on the "access" field. +func AccessContains(v string) predicate.Admin { + return predicate.Admin(sql.FieldContains(FieldAccess, v)) +} + +// AccessHasPrefix applies the HasPrefix predicate on the "access" field. +func AccessHasPrefix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasPrefix(FieldAccess, v)) +} + +// AccessHasSuffix applies the HasSuffix predicate on the "access" field. +func AccessHasSuffix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasSuffix(FieldAccess, v)) +} + +// AccessEqualFold applies the EqualFold predicate on the "access" field. +func AccessEqualFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldEqualFold(FieldAccess, v)) +} + +// AccessContainsFold applies the ContainsFold predicate on the "access" field. +func AccessContainsFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldContainsFold(FieldAccess, v)) +} + +// PasswordEQ applies the EQ predicate on the "password" field. +func PasswordEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldPassword, v)) +} + +// PasswordNEQ applies the NEQ predicate on the "password" field. +func PasswordNEQ(v string) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldPassword, v)) +} + +// PasswordIn applies the In predicate on the "password" field. +func PasswordIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldPassword, vs...)) +} + +// PasswordNotIn applies the NotIn predicate on the "password" field. +func PasswordNotIn(vs ...string) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldPassword, vs...)) +} + +// PasswordGT applies the GT predicate on the "password" field. +func PasswordGT(v string) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldPassword, v)) +} + +// PasswordGTE applies the GTE predicate on the "password" field. +func PasswordGTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldPassword, v)) +} + +// PasswordLT applies the LT predicate on the "password" field. +func PasswordLT(v string) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldPassword, v)) +} + +// PasswordLTE applies the LTE predicate on the "password" field. +func PasswordLTE(v string) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldPassword, v)) +} + +// PasswordContains applies the Contains predicate on the "password" field. +func PasswordContains(v string) predicate.Admin { + return predicate.Admin(sql.FieldContains(FieldPassword, v)) +} + +// PasswordHasPrefix applies the HasPrefix predicate on the "password" field. +func PasswordHasPrefix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasPrefix(FieldPassword, v)) +} + +// PasswordHasSuffix applies the HasSuffix predicate on the "password" field. +func PasswordHasSuffix(v string) predicate.Admin { + return predicate.Admin(sql.FieldHasSuffix(FieldPassword, v)) +} + +// PasswordEqualFold applies the EqualFold predicate on the "password" field. +func PasswordEqualFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldEqualFold(FieldPassword, v)) +} + +// PasswordContainsFold applies the ContainsFold predicate on the "password" field. +func PasswordContainsFold(v string) predicate.Admin { + return predicate.Admin(sql.FieldContainsFold(FieldPassword, v)) +} + +// CreateTimeEQ applies the EQ predicate on the "create_time" field. +func CreateTimeEQ(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldCreateTime, v)) +} + +// CreateTimeNEQ applies the NEQ predicate on the "create_time" field. +func CreateTimeNEQ(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldCreateTime, v)) +} + +// CreateTimeIn applies the In predicate on the "create_time" field. +func CreateTimeIn(vs ...time.Time) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldCreateTime, vs...)) +} + +// CreateTimeNotIn applies the NotIn predicate on the "create_time" field. +func CreateTimeNotIn(vs ...time.Time) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldCreateTime, vs...)) +} + +// CreateTimeGT applies the GT predicate on the "create_time" field. +func CreateTimeGT(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldCreateTime, v)) +} + +// CreateTimeGTE applies the GTE predicate on the "create_time" field. +func CreateTimeGTE(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldCreateTime, v)) +} + +// CreateTimeLT applies the LT predicate on the "create_time" field. +func CreateTimeLT(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldCreateTime, v)) +} + +// CreateTimeLTE applies the LTE predicate on the "create_time" field. +func CreateTimeLTE(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldCreateTime, v)) +} + +// UpdateTimeEQ applies the EQ predicate on the "update_time" field. +func UpdateTimeEQ(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldEQ(FieldUpdateTime, v)) +} + +// UpdateTimeNEQ applies the NEQ predicate on the "update_time" field. +func UpdateTimeNEQ(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldNEQ(FieldUpdateTime, v)) +} + +// UpdateTimeIn applies the In predicate on the "update_time" field. +func UpdateTimeIn(vs ...time.Time) predicate.Admin { + return predicate.Admin(sql.FieldIn(FieldUpdateTime, vs...)) +} + +// UpdateTimeNotIn applies the NotIn predicate on the "update_time" field. +func UpdateTimeNotIn(vs ...time.Time) predicate.Admin { + return predicate.Admin(sql.FieldNotIn(FieldUpdateTime, vs...)) +} + +// UpdateTimeGT applies the GT predicate on the "update_time" field. +func UpdateTimeGT(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldGT(FieldUpdateTime, v)) +} + +// UpdateTimeGTE applies the GTE predicate on the "update_time" field. +func UpdateTimeGTE(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldGTE(FieldUpdateTime, v)) +} + +// UpdateTimeLT applies the LT predicate on the "update_time" field. +func UpdateTimeLT(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldLT(FieldUpdateTime, v)) +} + +// UpdateTimeLTE applies the LTE predicate on the "update_time" field. +func UpdateTimeLTE(v time.Time) predicate.Admin { + return predicate.Admin(sql.FieldLTE(FieldUpdateTime, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Admin) predicate.Admin { + return predicate.Admin(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Admin) predicate.Admin { + return predicate.Admin(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Admin) predicate.Admin { + return predicate.Admin(sql.NotPredicates(p)) +} diff --git a/internal/data/ent/admin_create.go b/internal/data/ent/admin_create.go new file mode 100644 index 0000000..a9fc110 --- /dev/null +++ b/internal/data/ent/admin_create.go @@ -0,0 +1,859 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "kra/internal/data/ent/admin" +) + +// AdminCreate is the builder for creating a Admin entity. +type AdminCreate struct { + config + mutation *AdminMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetName sets the "name" field. +func (_c *AdminCreate) SetName(v string) *AdminCreate { + _c.mutation.SetName(v) + return _c +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_c *AdminCreate) SetNillableName(v *string) *AdminCreate { + if v != nil { + _c.SetName(*v) + } + return _c +} + +// SetEmail sets the "email" field. +func (_c *AdminCreate) SetEmail(v string) *AdminCreate { + _c.mutation.SetEmail(v) + return _c +} + +// SetNillableEmail sets the "email" field if the given value is not nil. +func (_c *AdminCreate) SetNillableEmail(v *string) *AdminCreate { + if v != nil { + _c.SetEmail(*v) + } + return _c +} + +// SetAvatar sets the "avatar" field. +func (_c *AdminCreate) SetAvatar(v string) *AdminCreate { + _c.mutation.SetAvatar(v) + return _c +} + +// SetNillableAvatar sets the "avatar" field if the given value is not nil. +func (_c *AdminCreate) SetNillableAvatar(v *string) *AdminCreate { + if v != nil { + _c.SetAvatar(*v) + } + return _c +} + +// SetAccess sets the "access" field. +func (_c *AdminCreate) SetAccess(v string) *AdminCreate { + _c.mutation.SetAccess(v) + return _c +} + +// SetNillableAccess sets the "access" field if the given value is not nil. +func (_c *AdminCreate) SetNillableAccess(v *string) *AdminCreate { + if v != nil { + _c.SetAccess(*v) + } + return _c +} + +// SetPassword sets the "password" field. +func (_c *AdminCreate) SetPassword(v string) *AdminCreate { + _c.mutation.SetPassword(v) + return _c +} + +// SetNillablePassword sets the "password" field if the given value is not nil. +func (_c *AdminCreate) SetNillablePassword(v *string) *AdminCreate { + if v != nil { + _c.SetPassword(*v) + } + return _c +} + +// SetCreateTime sets the "create_time" field. +func (_c *AdminCreate) SetCreateTime(v time.Time) *AdminCreate { + _c.mutation.SetCreateTime(v) + return _c +} + +// SetNillableCreateTime sets the "create_time" field if the given value is not nil. +func (_c *AdminCreate) SetNillableCreateTime(v *time.Time) *AdminCreate { + if v != nil { + _c.SetCreateTime(*v) + } + return _c +} + +// SetUpdateTime sets the "update_time" field. +func (_c *AdminCreate) SetUpdateTime(v time.Time) *AdminCreate { + _c.mutation.SetUpdateTime(v) + return _c +} + +// SetNillableUpdateTime sets the "update_time" field if the given value is not nil. +func (_c *AdminCreate) SetNillableUpdateTime(v *time.Time) *AdminCreate { + if v != nil { + _c.SetUpdateTime(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *AdminCreate) SetID(v int64) *AdminCreate { + _c.mutation.SetID(v) + return _c +} + +// Mutation returns the AdminMutation object of the builder. +func (_c *AdminCreate) Mutation() *AdminMutation { + return _c.mutation +} + +// Save creates the Admin in the database. +func (_c *AdminCreate) Save(ctx context.Context) (*Admin, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *AdminCreate) SaveX(ctx context.Context) *Admin { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *AdminCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *AdminCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *AdminCreate) defaults() { + if _, ok := _c.mutation.Name(); !ok { + v := admin.DefaultName + _c.mutation.SetName(v) + } + if _, ok := _c.mutation.Email(); !ok { + v := admin.DefaultEmail + _c.mutation.SetEmail(v) + } + if _, ok := _c.mutation.Avatar(); !ok { + v := admin.DefaultAvatar + _c.mutation.SetAvatar(v) + } + if _, ok := _c.mutation.Access(); !ok { + v := admin.DefaultAccess + _c.mutation.SetAccess(v) + } + if _, ok := _c.mutation.Password(); !ok { + v := admin.DefaultPassword + _c.mutation.SetPassword(v) + } + if _, ok := _c.mutation.CreateTime(); !ok { + v := admin.DefaultCreateTime() + _c.mutation.SetCreateTime(v) + } + if _, ok := _c.mutation.UpdateTime(); !ok { + v := admin.DefaultUpdateTime() + _c.mutation.SetUpdateTime(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *AdminCreate) check() error { + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Admin.name"`)} + } + if _, ok := _c.mutation.Email(); !ok { + return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "Admin.email"`)} + } + if _, ok := _c.mutation.Avatar(); !ok { + return &ValidationError{Name: "avatar", err: errors.New(`ent: missing required field "Admin.avatar"`)} + } + if _, ok := _c.mutation.Access(); !ok { + return &ValidationError{Name: "access", err: errors.New(`ent: missing required field "Admin.access"`)} + } + if _, ok := _c.mutation.Password(); !ok { + return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "Admin.password"`)} + } + if _, ok := _c.mutation.CreateTime(); !ok { + return &ValidationError{Name: "create_time", err: errors.New(`ent: missing required field "Admin.create_time"`)} + } + if _, ok := _c.mutation.UpdateTime(); !ok { + return &ValidationError{Name: "update_time", err: errors.New(`ent: missing required field "Admin.update_time"`)} + } + return nil +} + +func (_c *AdminCreate) sqlSave(ctx context.Context) (*Admin, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != _node.ID { + id := _spec.ID.Value.(int64) + _node.ID = int64(id) + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *AdminCreate) createSpec() (*Admin, *sqlgraph.CreateSpec) { + var ( + _node = &Admin{config: _c.config} + _spec = sqlgraph.NewCreateSpec(admin.Table, sqlgraph.NewFieldSpec(admin.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(admin.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Email(); ok { + _spec.SetField(admin.FieldEmail, field.TypeString, value) + _node.Email = value + } + if value, ok := _c.mutation.Avatar(); ok { + _spec.SetField(admin.FieldAvatar, field.TypeString, value) + _node.Avatar = value + } + if value, ok := _c.mutation.Access(); ok { + _spec.SetField(admin.FieldAccess, field.TypeString, value) + _node.Access = value + } + if value, ok := _c.mutation.Password(); ok { + _spec.SetField(admin.FieldPassword, field.TypeString, value) + _node.Password = value + } + if value, ok := _c.mutation.CreateTime(); ok { + _spec.SetField(admin.FieldCreateTime, field.TypeTime, value) + _node.CreateTime = value + } + if value, ok := _c.mutation.UpdateTime(); ok { + _spec.SetField(admin.FieldUpdateTime, field.TypeTime, value) + _node.UpdateTime = value + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.Admin.Create(). +// SetName(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.AdminUpsert) { +// SetName(v+v). +// }). +// Exec(ctx) +func (_c *AdminCreate) OnConflict(opts ...sql.ConflictOption) *AdminUpsertOne { + _c.conflict = opts + return &AdminUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.Admin.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *AdminCreate) OnConflictColumns(columns ...string) *AdminUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &AdminUpsertOne{ + create: _c, + } +} + +type ( + // AdminUpsertOne is the builder for "upsert"-ing + // one Admin node. + AdminUpsertOne struct { + create *AdminCreate + } + + // AdminUpsert is the "OnConflict" setter. + AdminUpsert struct { + *sql.UpdateSet + } +) + +// SetName sets the "name" field. +func (u *AdminUpsert) SetName(v string) *AdminUpsert { + u.Set(admin.FieldName, v) + return u +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *AdminUpsert) UpdateName() *AdminUpsert { + u.SetExcluded(admin.FieldName) + return u +} + +// SetEmail sets the "email" field. +func (u *AdminUpsert) SetEmail(v string) *AdminUpsert { + u.Set(admin.FieldEmail, v) + return u +} + +// UpdateEmail sets the "email" field to the value that was provided on create. +func (u *AdminUpsert) UpdateEmail() *AdminUpsert { + u.SetExcluded(admin.FieldEmail) + return u +} + +// SetAvatar sets the "avatar" field. +func (u *AdminUpsert) SetAvatar(v string) *AdminUpsert { + u.Set(admin.FieldAvatar, v) + return u +} + +// UpdateAvatar sets the "avatar" field to the value that was provided on create. +func (u *AdminUpsert) UpdateAvatar() *AdminUpsert { + u.SetExcluded(admin.FieldAvatar) + return u +} + +// SetAccess sets the "access" field. +func (u *AdminUpsert) SetAccess(v string) *AdminUpsert { + u.Set(admin.FieldAccess, v) + return u +} + +// UpdateAccess sets the "access" field to the value that was provided on create. +func (u *AdminUpsert) UpdateAccess() *AdminUpsert { + u.SetExcluded(admin.FieldAccess) + return u +} + +// SetPassword sets the "password" field. +func (u *AdminUpsert) SetPassword(v string) *AdminUpsert { + u.Set(admin.FieldPassword, v) + return u +} + +// UpdatePassword sets the "password" field to the value that was provided on create. +func (u *AdminUpsert) UpdatePassword() *AdminUpsert { + u.SetExcluded(admin.FieldPassword) + return u +} + +// SetUpdateTime sets the "update_time" field. +func (u *AdminUpsert) SetUpdateTime(v time.Time) *AdminUpsert { + u.Set(admin.FieldUpdateTime, v) + return u +} + +// UpdateUpdateTime sets the "update_time" field to the value that was provided on create. +func (u *AdminUpsert) UpdateUpdateTime() *AdminUpsert { + u.SetExcluded(admin.FieldUpdateTime) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. +// Using this option is equivalent to using: +// +// client.Admin.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(admin.FieldID) +// }), +// ). +// Exec(ctx) +func (u *AdminUpsertOne) UpdateNewValues() *AdminUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + if _, exists := u.create.mutation.ID(); exists { + s.SetIgnore(admin.FieldID) + } + if _, exists := u.create.mutation.CreateTime(); exists { + s.SetIgnore(admin.FieldCreateTime) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.Admin.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *AdminUpsertOne) Ignore() *AdminUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *AdminUpsertOne) DoNothing() *AdminUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the AdminCreate.OnConflict +// documentation for more info. +func (u *AdminUpsertOne) Update(set func(*AdminUpsert)) *AdminUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&AdminUpsert{UpdateSet: update}) + })) + return u +} + +// SetName sets the "name" field. +func (u *AdminUpsertOne) SetName(v string) *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *AdminUpsertOne) UpdateName() *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.UpdateName() + }) +} + +// SetEmail sets the "email" field. +func (u *AdminUpsertOne) SetEmail(v string) *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.SetEmail(v) + }) +} + +// UpdateEmail sets the "email" field to the value that was provided on create. +func (u *AdminUpsertOne) UpdateEmail() *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.UpdateEmail() + }) +} + +// SetAvatar sets the "avatar" field. +func (u *AdminUpsertOne) SetAvatar(v string) *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.SetAvatar(v) + }) +} + +// UpdateAvatar sets the "avatar" field to the value that was provided on create. +func (u *AdminUpsertOne) UpdateAvatar() *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.UpdateAvatar() + }) +} + +// SetAccess sets the "access" field. +func (u *AdminUpsertOne) SetAccess(v string) *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.SetAccess(v) + }) +} + +// UpdateAccess sets the "access" field to the value that was provided on create. +func (u *AdminUpsertOne) UpdateAccess() *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.UpdateAccess() + }) +} + +// SetPassword sets the "password" field. +func (u *AdminUpsertOne) SetPassword(v string) *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.SetPassword(v) + }) +} + +// UpdatePassword sets the "password" field to the value that was provided on create. +func (u *AdminUpsertOne) UpdatePassword() *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.UpdatePassword() + }) +} + +// SetUpdateTime sets the "update_time" field. +func (u *AdminUpsertOne) SetUpdateTime(v time.Time) *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.SetUpdateTime(v) + }) +} + +// UpdateUpdateTime sets the "update_time" field to the value that was provided on create. +func (u *AdminUpsertOne) UpdateUpdateTime() *AdminUpsertOne { + return u.Update(func(s *AdminUpsert) { + s.UpdateUpdateTime() + }) +} + +// Exec executes the query. +func (u *AdminUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for AdminCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *AdminUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *AdminUpsertOne) ID(ctx context.Context) (id int64, err error) { + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *AdminUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// AdminCreateBulk is the builder for creating many Admin entities in bulk. +type AdminCreateBulk struct { + config + err error + builders []*AdminCreate + conflict []sql.ConflictOption +} + +// Save creates the Admin entities in the database. +func (_c *AdminCreateBulk) Save(ctx context.Context) ([]*Admin, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*Admin, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AdminMutation) + 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, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = _c.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.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 && nodes[i].ID == 0 { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int64(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, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *AdminCreateBulk) SaveX(ctx context.Context) []*Admin { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *AdminCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *AdminCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.Admin.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.AdminUpsert) { +// SetName(v+v). +// }). +// Exec(ctx) +func (_c *AdminCreateBulk) OnConflict(opts ...sql.ConflictOption) *AdminUpsertBulk { + _c.conflict = opts + return &AdminUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.Admin.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *AdminCreateBulk) OnConflictColumns(columns ...string) *AdminUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &AdminUpsertBulk{ + create: _c, + } +} + +// AdminUpsertBulk is the builder for "upsert"-ing +// a bulk of Admin nodes. +type AdminUpsertBulk struct { + create *AdminCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.Admin.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(admin.FieldID) +// }), +// ). +// Exec(ctx) +func (u *AdminUpsertBulk) UpdateNewValues() *AdminUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + for _, b := range u.create.builders { + if _, exists := b.mutation.ID(); exists { + s.SetIgnore(admin.FieldID) + } + if _, exists := b.mutation.CreateTime(); exists { + s.SetIgnore(admin.FieldCreateTime) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.Admin.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *AdminUpsertBulk) Ignore() *AdminUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *AdminUpsertBulk) DoNothing() *AdminUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the AdminCreateBulk.OnConflict +// documentation for more info. +func (u *AdminUpsertBulk) Update(set func(*AdminUpsert)) *AdminUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&AdminUpsert{UpdateSet: update}) + })) + return u +} + +// SetName sets the "name" field. +func (u *AdminUpsertBulk) SetName(v string) *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *AdminUpsertBulk) UpdateName() *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.UpdateName() + }) +} + +// SetEmail sets the "email" field. +func (u *AdminUpsertBulk) SetEmail(v string) *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.SetEmail(v) + }) +} + +// UpdateEmail sets the "email" field to the value that was provided on create. +func (u *AdminUpsertBulk) UpdateEmail() *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.UpdateEmail() + }) +} + +// SetAvatar sets the "avatar" field. +func (u *AdminUpsertBulk) SetAvatar(v string) *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.SetAvatar(v) + }) +} + +// UpdateAvatar sets the "avatar" field to the value that was provided on create. +func (u *AdminUpsertBulk) UpdateAvatar() *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.UpdateAvatar() + }) +} + +// SetAccess sets the "access" field. +func (u *AdminUpsertBulk) SetAccess(v string) *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.SetAccess(v) + }) +} + +// UpdateAccess sets the "access" field to the value that was provided on create. +func (u *AdminUpsertBulk) UpdateAccess() *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.UpdateAccess() + }) +} + +// SetPassword sets the "password" field. +func (u *AdminUpsertBulk) SetPassword(v string) *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.SetPassword(v) + }) +} + +// UpdatePassword sets the "password" field to the value that was provided on create. +func (u *AdminUpsertBulk) UpdatePassword() *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.UpdatePassword() + }) +} + +// SetUpdateTime sets the "update_time" field. +func (u *AdminUpsertBulk) SetUpdateTime(v time.Time) *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.SetUpdateTime(v) + }) +} + +// UpdateUpdateTime sets the "update_time" field to the value that was provided on create. +func (u *AdminUpsertBulk) UpdateUpdateTime() *AdminUpsertBulk { + return u.Update(func(s *AdminUpsert) { + s.UpdateUpdateTime() + }) +} + +// Exec executes the query. +func (u *AdminUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the AdminCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for AdminCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *AdminUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/data/ent/admin_delete.go b/internal/data/ent/admin_delete.go new file mode 100644 index 0000000..881482f --- /dev/null +++ b/internal/data/ent/admin_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" + "kra/internal/data/ent/admin" + "kra/internal/data/ent/predicate" +) + +// AdminDelete is the builder for deleting a Admin entity. +type AdminDelete struct { + config + hooks []Hook + mutation *AdminMutation +} + +// Where appends a list predicates to the AdminDelete builder. +func (_d *AdminDelete) Where(ps ...predicate.Admin) *AdminDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *AdminDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *AdminDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *AdminDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(admin.Table, sqlgraph.NewFieldSpec(admin.FieldID, field.TypeInt64)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// AdminDeleteOne is the builder for deleting a single Admin entity. +type AdminDeleteOne struct { + _d *AdminDelete +} + +// Where appends a list predicates to the AdminDelete builder. +func (_d *AdminDeleteOne) Where(ps ...predicate.Admin) *AdminDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *AdminDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{admin.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *AdminDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/data/ent/admin_query.go b/internal/data/ent/admin_query.go new file mode 100644 index 0000000..4192c01 --- /dev/null +++ b/internal/data/ent/admin_query.go @@ -0,0 +1,527 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "kra/internal/data/ent/admin" + "kra/internal/data/ent/predicate" +) + +// AdminQuery is the builder for querying Admin entities. +type AdminQuery struct { + config + ctx *QueryContext + order []admin.OrderOption + inters []Interceptor + predicates []predicate.Admin + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AdminQuery builder. +func (_q *AdminQuery) Where(ps ...predicate.Admin) *AdminQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *AdminQuery) Limit(limit int) *AdminQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *AdminQuery) Offset(offset int) *AdminQuery { + _q.ctx.Offset = &offset + return _q +} + +// 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 (_q *AdminQuery) Unique(unique bool) *AdminQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *AdminQuery) Order(o ...admin.OrderOption) *AdminQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first Admin entity from the query. +// Returns a *NotFoundError when no Admin was found. +func (_q *AdminQuery) First(ctx context.Context) (*Admin, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{admin.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *AdminQuery) FirstX(ctx context.Context) *Admin { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Admin ID from the query. +// Returns a *NotFoundError when no Admin ID was found. +func (_q *AdminQuery) FirstID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{admin.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *AdminQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Admin entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Admin entity is found. +// Returns a *NotFoundError when no Admin entities are found. +func (_q *AdminQuery) Only(ctx context.Context) (*Admin, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{admin.Label} + default: + return nil, &NotSingularError{admin.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *AdminQuery) OnlyX(ctx context.Context) *Admin { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Admin ID in the query. +// Returns a *NotSingularError when more than one Admin ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *AdminQuery) OnlyID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{admin.Label} + default: + err = &NotSingularError{admin.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *AdminQuery) OnlyIDX(ctx context.Context) int64 { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Admins. +func (_q *AdminQuery) All(ctx context.Context) ([]*Admin, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Admin, *AdminQuery]() + return withInterceptors[[]*Admin](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *AdminQuery) AllX(ctx context.Context) []*Admin { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Admin IDs. +func (_q *AdminQuery) IDs(ctx context.Context) (ids []int64, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(admin.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *AdminQuery) IDsX(ctx context.Context) []int64 { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *AdminQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*AdminQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *AdminQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *AdminQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.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 (_q *AdminQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AdminQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *AdminQuery) Clone() *AdminQuery { + if _q == nil { + return nil + } + return &AdminQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]admin.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.Admin{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// 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.Admin.Query(). +// GroupBy(admin.FieldName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *AdminQuery) GroupBy(field string, fields ...string) *AdminGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &AdminGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = admin.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.Admin.Query(). +// Select(admin.FieldName). +// Scan(ctx, &v) +func (_q *AdminQuery) Select(fields ...string) *AdminSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &AdminSelect{AdminQuery: _q} + sbuild.label = admin.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AdminSelect configured with the given aggregations. +func (_q *AdminQuery) Aggregate(fns ...AggregateFunc) *AdminSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *AdminQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.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, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !admin.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *AdminQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Admin, error) { + var ( + nodes = []*Admin{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Admin).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Admin{config: _q.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *AdminQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *AdminQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(admin.Table, admin.Columns, sqlgraph.NewFieldSpec(admin.FieldID, field.TypeInt64)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, admin.FieldID) + for i := range fields { + if fields[i] != admin.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *AdminQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(admin.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = admin.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.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 := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AdminGroupBy is the group-by builder for Admin entities. +type AdminGroupBy struct { + selector + build *AdminQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *AdminGroupBy) Aggregate(fns ...AggregateFunc) *AdminGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *AdminGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AdminQuery, *AdminGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *AdminGroupBy) sqlScan(ctx context.Context, root *AdminQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AdminSelect is the builder for selecting fields of Admin entities. +type AdminSelect struct { + *AdminQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *AdminSelect) Aggregate(fns ...AggregateFunc) *AdminSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *AdminSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AdminQuery, *AdminSelect](ctx, _s.AdminQuery, _s, _s.inters, v) +} + +func (_s *AdminSelect) sqlScan(ctx context.Context, root *AdminQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.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 := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/data/ent/admin_update.go b/internal/data/ent/admin_update.go new file mode 100644 index 0000000..ca468b8 --- /dev/null +++ b/internal/data/ent/admin_update.go @@ -0,0 +1,382 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "kra/internal/data/ent/admin" + "kra/internal/data/ent/predicate" +) + +// AdminUpdate is the builder for updating Admin entities. +type AdminUpdate struct { + config + hooks []Hook + mutation *AdminMutation +} + +// Where appends a list predicates to the AdminUpdate builder. +func (_u *AdminUpdate) Where(ps ...predicate.Admin) *AdminUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetName sets the "name" field. +func (_u *AdminUpdate) SetName(v string) *AdminUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *AdminUpdate) SetNillableName(v *string) *AdminUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetEmail sets the "email" field. +func (_u *AdminUpdate) SetEmail(v string) *AdminUpdate { + _u.mutation.SetEmail(v) + return _u +} + +// SetNillableEmail sets the "email" field if the given value is not nil. +func (_u *AdminUpdate) SetNillableEmail(v *string) *AdminUpdate { + if v != nil { + _u.SetEmail(*v) + } + return _u +} + +// SetAvatar sets the "avatar" field. +func (_u *AdminUpdate) SetAvatar(v string) *AdminUpdate { + _u.mutation.SetAvatar(v) + return _u +} + +// SetNillableAvatar sets the "avatar" field if the given value is not nil. +func (_u *AdminUpdate) SetNillableAvatar(v *string) *AdminUpdate { + if v != nil { + _u.SetAvatar(*v) + } + return _u +} + +// SetAccess sets the "access" field. +func (_u *AdminUpdate) SetAccess(v string) *AdminUpdate { + _u.mutation.SetAccess(v) + return _u +} + +// SetNillableAccess sets the "access" field if the given value is not nil. +func (_u *AdminUpdate) SetNillableAccess(v *string) *AdminUpdate { + if v != nil { + _u.SetAccess(*v) + } + return _u +} + +// SetPassword sets the "password" field. +func (_u *AdminUpdate) SetPassword(v string) *AdminUpdate { + _u.mutation.SetPassword(v) + return _u +} + +// SetNillablePassword sets the "password" field if the given value is not nil. +func (_u *AdminUpdate) SetNillablePassword(v *string) *AdminUpdate { + if v != nil { + _u.SetPassword(*v) + } + return _u +} + +// SetUpdateTime sets the "update_time" field. +func (_u *AdminUpdate) SetUpdateTime(v time.Time) *AdminUpdate { + _u.mutation.SetUpdateTime(v) + return _u +} + +// Mutation returns the AdminMutation object of the builder. +func (_u *AdminUpdate) Mutation() *AdminMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *AdminUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *AdminUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *AdminUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *AdminUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *AdminUpdate) defaults() { + if _, ok := _u.mutation.UpdateTime(); !ok { + v := admin.UpdateDefaultUpdateTime() + _u.mutation.SetUpdateTime(v) + } +} + +func (_u *AdminUpdate) sqlSave(ctx context.Context) (_node int, err error) { + _spec := sqlgraph.NewUpdateSpec(admin.Table, admin.Columns, sqlgraph.NewFieldSpec(admin.FieldID, field.TypeInt64)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(admin.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Email(); ok { + _spec.SetField(admin.FieldEmail, field.TypeString, value) + } + if value, ok := _u.mutation.Avatar(); ok { + _spec.SetField(admin.FieldAvatar, field.TypeString, value) + } + if value, ok := _u.mutation.Access(); ok { + _spec.SetField(admin.FieldAccess, field.TypeString, value) + } + if value, ok := _u.mutation.Password(); ok { + _spec.SetField(admin.FieldPassword, field.TypeString, value) + } + if value, ok := _u.mutation.UpdateTime(); ok { + _spec.SetField(admin.FieldUpdateTime, field.TypeTime, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{admin.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// AdminUpdateOne is the builder for updating a single Admin entity. +type AdminUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AdminMutation +} + +// SetName sets the "name" field. +func (_u *AdminUpdateOne) SetName(v string) *AdminUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *AdminUpdateOne) SetNillableName(v *string) *AdminUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetEmail sets the "email" field. +func (_u *AdminUpdateOne) SetEmail(v string) *AdminUpdateOne { + _u.mutation.SetEmail(v) + return _u +} + +// SetNillableEmail sets the "email" field if the given value is not nil. +func (_u *AdminUpdateOne) SetNillableEmail(v *string) *AdminUpdateOne { + if v != nil { + _u.SetEmail(*v) + } + return _u +} + +// SetAvatar sets the "avatar" field. +func (_u *AdminUpdateOne) SetAvatar(v string) *AdminUpdateOne { + _u.mutation.SetAvatar(v) + return _u +} + +// SetNillableAvatar sets the "avatar" field if the given value is not nil. +func (_u *AdminUpdateOne) SetNillableAvatar(v *string) *AdminUpdateOne { + if v != nil { + _u.SetAvatar(*v) + } + return _u +} + +// SetAccess sets the "access" field. +func (_u *AdminUpdateOne) SetAccess(v string) *AdminUpdateOne { + _u.mutation.SetAccess(v) + return _u +} + +// SetNillableAccess sets the "access" field if the given value is not nil. +func (_u *AdminUpdateOne) SetNillableAccess(v *string) *AdminUpdateOne { + if v != nil { + _u.SetAccess(*v) + } + return _u +} + +// SetPassword sets the "password" field. +func (_u *AdminUpdateOne) SetPassword(v string) *AdminUpdateOne { + _u.mutation.SetPassword(v) + return _u +} + +// SetNillablePassword sets the "password" field if the given value is not nil. +func (_u *AdminUpdateOne) SetNillablePassword(v *string) *AdminUpdateOne { + if v != nil { + _u.SetPassword(*v) + } + return _u +} + +// SetUpdateTime sets the "update_time" field. +func (_u *AdminUpdateOne) SetUpdateTime(v time.Time) *AdminUpdateOne { + _u.mutation.SetUpdateTime(v) + return _u +} + +// Mutation returns the AdminMutation object of the builder. +func (_u *AdminUpdateOne) Mutation() *AdminMutation { + return _u.mutation +} + +// Where appends a list predicates to the AdminUpdate builder. +func (_u *AdminUpdateOne) Where(ps ...predicate.Admin) *AdminUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *AdminUpdateOne) Select(field string, fields ...string) *AdminUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated Admin entity. +func (_u *AdminUpdateOne) Save(ctx context.Context) (*Admin, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *AdminUpdateOne) SaveX(ctx context.Context) *Admin { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *AdminUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *AdminUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *AdminUpdateOne) defaults() { + if _, ok := _u.mutation.UpdateTime(); !ok { + v := admin.UpdateDefaultUpdateTime() + _u.mutation.SetUpdateTime(v) + } +} + +func (_u *AdminUpdateOne) sqlSave(ctx context.Context) (_node *Admin, err error) { + _spec := sqlgraph.NewUpdateSpec(admin.Table, admin.Columns, sqlgraph.NewFieldSpec(admin.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Admin.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, admin.FieldID) + for _, f := range fields { + if !admin.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != admin.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(admin.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Email(); ok { + _spec.SetField(admin.FieldEmail, field.TypeString, value) + } + if value, ok := _u.mutation.Avatar(); ok { + _spec.SetField(admin.FieldAvatar, field.TypeString, value) + } + if value, ok := _u.mutation.Access(); ok { + _spec.SetField(admin.FieldAccess, field.TypeString, value) + } + if value, ok := _u.mutation.Password(); ok { + _spec.SetField(admin.FieldPassword, field.TypeString, value) + } + if value, ok := _u.mutation.UpdateTime(); ok { + _spec.SetField(admin.FieldUpdateTime, field.TypeTime, value) + } + _node = &Admin{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{admin.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/internal/data/ent/client.go b/internal/data/ent/client.go new file mode 100644 index 0000000..a363e2d --- /dev/null +++ b/internal/data/ent/client.go @@ -0,0 +1,366 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + + "kra/internal/data/ent/migrate" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "kra/internal/data/ent/admin" + + stdsql "database/sql" +) + +// 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 + // Admin is the client for interacting with the Admin builders. + Admin *AdminClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + client := &Client{config: newConfig(opts...)} + client.init() + return client +} + +func (c *Client) init() { + c.Schema = migrate.NewSchema(c.driver) + c.Admin = NewAdminClient(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) +) + +// newConfig creates a new config for the client. +func newConfig(opts ...Option) config { + cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} + cfg.options(opts...) + return cfg +} + +// 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) + } +} + +// ErrTxStarted is returned when trying to start a new transaction from a transactional client. +var ErrTxStarted = errors.New("ent: cannot start a transaction within a transaction") + +// 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, ErrTxStarted + } + 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, + Admin: NewAdminClient(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, + Admin: NewAdminClient(cfg), + }, nil +} + +// Debug returns a new debug-client. It's used to get verbose logging on specific operations. +// +// client.Debug(). +// Admin. +// 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.Admin.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.Admin.Intercept(interceptors...) +} + +// Mutate implements the ent.Mutator interface. +func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { + switch m := m.(type) { + case *AdminMutation: + return c.Admin.mutate(ctx, m) + default: + return nil, fmt.Errorf("ent: unknown mutation type %T", m) + } +} + +// AdminClient is a client for the Admin schema. +type AdminClient struct { + config +} + +// NewAdminClient returns a client for the Admin from the given config. +func NewAdminClient(c config) *AdminClient { + return &AdminClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `admin.Hooks(f(g(h())))`. +func (c *AdminClient) Use(hooks ...Hook) { + c.hooks.Admin = append(c.hooks.Admin, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `admin.Intercept(f(g(h())))`. +func (c *AdminClient) Intercept(interceptors ...Interceptor) { + c.inters.Admin = append(c.inters.Admin, interceptors...) +} + +// Create returns a builder for creating a Admin entity. +func (c *AdminClient) Create() *AdminCreate { + mutation := newAdminMutation(c.config, OpCreate) + return &AdminCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Admin entities. +func (c *AdminClient) CreateBulk(builders ...*AdminCreate) *AdminCreateBulk { + return &AdminCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AdminClient) MapCreateBulk(slice any, setFunc func(*AdminCreate, int)) *AdminCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AdminCreateBulk{err: fmt.Errorf("calling to AdminClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AdminCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AdminCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Admin. +func (c *AdminClient) Update() *AdminUpdate { + mutation := newAdminMutation(c.config, OpUpdate) + return &AdminUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AdminClient) UpdateOne(_m *Admin) *AdminUpdateOne { + mutation := newAdminMutation(c.config, OpUpdateOne, withAdmin(_m)) + return &AdminUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AdminClient) UpdateOneID(id int64) *AdminUpdateOne { + mutation := newAdminMutation(c.config, OpUpdateOne, withAdminID(id)) + return &AdminUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Admin. +func (c *AdminClient) Delete() *AdminDelete { + mutation := newAdminMutation(c.config, OpDelete) + return &AdminDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AdminClient) DeleteOne(_m *Admin) *AdminDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AdminClient) DeleteOneID(id int64) *AdminDeleteOne { + builder := c.Delete().Where(admin.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AdminDeleteOne{builder} +} + +// Query returns a query builder for Admin. +func (c *AdminClient) Query() *AdminQuery { + return &AdminQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAdmin}, + inters: c.Interceptors(), + } +} + +// Get returns a Admin entity by its id. +func (c *AdminClient) Get(ctx context.Context, id int64) (*Admin, error) { + return c.Query().Where(admin.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AdminClient) GetX(ctx context.Context, id int64) *Admin { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *AdminClient) Hooks() []Hook { + return c.hooks.Admin +} + +// Interceptors returns the client interceptors. +func (c *AdminClient) Interceptors() []Interceptor { + return c.inters.Admin +} + +func (c *AdminClient) mutate(ctx context.Context, m *AdminMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AdminCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AdminUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AdminUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AdminDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Admin mutation op: %q", m.Op()) + } +} + +// hooks and interceptors per client, for fast access. +type ( + hooks struct { + Admin []ent.Hook + } + inters struct { + Admin []ent.Interceptor + } +) + +// ExecContext allows calling the underlying ExecContext method of the driver if it is supported by it. +// See, database/sql#DB.ExecContext for more information. +func (c *config) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) { + ex, ok := c.driver.(interface { + ExecContext(context.Context, string, ...any) (stdsql.Result, error) + }) + if !ok { + return nil, fmt.Errorf("Driver.ExecContext is not supported") + } + return ex.ExecContext(ctx, query, args...) +} + +// QueryContext allows calling the underlying QueryContext method of the driver if it is supported by it. +// See, database/sql#DB.QueryContext for more information. +func (c *config) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) { + q, ok := c.driver.(interface { + QueryContext(context.Context, string, ...any) (*stdsql.Rows, error) + }) + if !ok { + return nil, fmt.Errorf("Driver.QueryContext is not supported") + } + return q.QueryContext(ctx, query, args...) +} diff --git a/internal/data/ent/ent.go b/internal/data/ent/ent.go new file mode 100644 index 0000000..1976d56 --- /dev/null +++ b/internal/data/ent/ent.go @@ -0,0 +1,608 @@ +// 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" + "kra/internal/data/ent/admin" +) + +// 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 +) + +// checkColumn checks if the column exists in the given table. +func checkColumn(t, c string) error { + initCheck.Do(func() { + columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + admin.Table: admin.ValidColumn, + }) + }) + return columnCheck(t, c) +} + +// 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/internal/data/ent/enttest/enttest.go b/internal/data/ent/enttest/enttest.go new file mode 100644 index 0000000..6b9daaf --- /dev/null +++ b/internal/data/ent/enttest/enttest.go @@ -0,0 +1,84 @@ +// Code generated by ent, DO NOT EDIT. + +package enttest + +import ( + "context" + + "kra/internal/data/ent" + // required by schema hooks. + _ "kra/internal/data/ent/runtime" + + "entgo.io/ent/dialect/sql/schema" + "kra/internal/data/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/internal/data/ent/generate.go b/internal/data/ent/generate.go new file mode 100644 index 0000000..67985fa --- /dev/null +++ b/internal/data/ent/generate.go @@ -0,0 +1,3 @@ +package ent + +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema --feature sql/execquery --feature sql/upsert diff --git a/internal/data/ent/hook/hook.go b/internal/data/ent/hook/hook.go new file mode 100644 index 0000000..cb9a040 --- /dev/null +++ b/internal/data/ent/hook/hook.go @@ -0,0 +1,199 @@ +// Code generated by ent, DO NOT EDIT. + +package hook + +import ( + "context" + "fmt" + + "kra/internal/data/ent" +) + +// The AdminFunc type is an adapter to allow the use of ordinary +// function as Admin mutator. +type AdminFunc func(context.Context, *ent.AdminMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f AdminFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.AdminMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AdminMutation", 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/internal/data/ent/migrate/migrate.go b/internal/data/ent/migrate/migrate.go new file mode 100644 index 0000000..1956a6b --- /dev/null +++ b/internal/data/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/internal/data/ent/migrate/schema.go b/internal/data/ent/migrate/schema.go new file mode 100644 index 0000000..c140ea5 --- /dev/null +++ b/internal/data/ent/migrate/schema.go @@ -0,0 +1,35 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/schema/field" +) + +var ( + // AdminsColumns holds the columns for the "admins" table. + AdminsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "name", Type: field.TypeString, Default: ""}, + {Name: "email", Type: field.TypeString, Default: ""}, + {Name: "avatar", Type: field.TypeString, Default: ""}, + {Name: "access", Type: field.TypeString, Default: ""}, + {Name: "password", Type: field.TypeString, Default: ""}, + {Name: "create_time", Type: field.TypeTime}, + {Name: "update_time", Type: field.TypeTime}, + } + // AdminsTable holds the schema information for the "admins" table. + AdminsTable = &schema.Table{ + Name: "admins", + Columns: AdminsColumns, + PrimaryKey: []*schema.Column{AdminsColumns[0]}, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + AdminsTable, + } +) + +func init() { +} diff --git a/internal/data/ent/mutation.go b/internal/data/ent/mutation.go new file mode 100644 index 0000000..9ee90fa --- /dev/null +++ b/internal/data/ent/mutation.go @@ -0,0 +1,684 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "kra/internal/data/ent/admin" + "kra/internal/data/ent/predicate" +) + +const ( + // Operation types. + OpCreate = ent.OpCreate + OpDelete = ent.OpDelete + OpDeleteOne = ent.OpDeleteOne + OpUpdate = ent.OpUpdate + OpUpdateOne = ent.OpUpdateOne + + // Node types. + TypeAdmin = "Admin" +) + +// AdminMutation represents an operation that mutates the Admin nodes in the graph. +type AdminMutation struct { + config + op Op + typ string + id *int64 + name *string + email *string + avatar *string + access *string + password *string + create_time *time.Time + update_time *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*Admin, error) + predicates []predicate.Admin +} + +var _ ent.Mutation = (*AdminMutation)(nil) + +// adminOption allows management of the mutation configuration using functional options. +type adminOption func(*AdminMutation) + +// newAdminMutation creates new mutation for the Admin entity. +func newAdminMutation(c config, op Op, opts ...adminOption) *AdminMutation { + m := &AdminMutation{ + config: c, + op: op, + typ: TypeAdmin, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAdminID sets the ID field of the mutation. +func withAdminID(id int64) adminOption { + return func(m *AdminMutation) { + var ( + err error + once sync.Once + value *Admin + ) + m.oldValue = func(ctx context.Context) (*Admin, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Admin.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAdmin sets the old Admin of the mutation. +func withAdmin(node *Admin) adminOption { + return func(m *AdminMutation) { + m.oldValue = func(context.Context) (*Admin, 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 AdminMutation) 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 AdminMutation) 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 +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Admin entities. +func (m *AdminMutation) SetID(id int64) { + m.id = &id +} + +// 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 *AdminMutation) ID() (id int64, 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 *AdminMutation) IDs(ctx context.Context) ([]int64, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int64{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Admin.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 *AdminMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *AdminMutation) 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 Admin entity. +// If the Admin 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 *AdminMutation) 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 *AdminMutation) ResetName() { + m.name = nil +} + +// SetEmail sets the "email" field. +func (m *AdminMutation) SetEmail(s string) { + m.email = &s +} + +// Email returns the value of the "email" field in the mutation. +func (m *AdminMutation) 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 Admin entity. +// If the Admin 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 *AdminMutation) 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 *AdminMutation) ResetEmail() { + m.email = nil +} + +// SetAvatar sets the "avatar" field. +func (m *AdminMutation) SetAvatar(s string) { + m.avatar = &s +} + +// Avatar returns the value of the "avatar" field in the mutation. +func (m *AdminMutation) Avatar() (r string, exists bool) { + v := m.avatar + if v == nil { + return + } + return *v, true +} + +// OldAvatar returns the old "avatar" field's value of the Admin entity. +// If the Admin 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 *AdminMutation) OldAvatar(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAvatar is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAvatar requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAvatar: %w", err) + } + return oldValue.Avatar, nil +} + +// ResetAvatar resets all changes to the "avatar" field. +func (m *AdminMutation) ResetAvatar() { + m.avatar = nil +} + +// SetAccess sets the "access" field. +func (m *AdminMutation) SetAccess(s string) { + m.access = &s +} + +// Access returns the value of the "access" field in the mutation. +func (m *AdminMutation) Access() (r string, exists bool) { + v := m.access + if v == nil { + return + } + return *v, true +} + +// OldAccess returns the old "access" field's value of the Admin entity. +// If the Admin 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 *AdminMutation) OldAccess(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAccess is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAccess requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAccess: %w", err) + } + return oldValue.Access, nil +} + +// ResetAccess resets all changes to the "access" field. +func (m *AdminMutation) ResetAccess() { + m.access = nil +} + +// SetPassword sets the "password" field. +func (m *AdminMutation) SetPassword(s string) { + m.password = &s +} + +// Password returns the value of the "password" field in the mutation. +func (m *AdminMutation) 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 Admin entity. +// If the Admin 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 *AdminMutation) 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 *AdminMutation) ResetPassword() { + m.password = nil +} + +// SetCreateTime sets the "create_time" field. +func (m *AdminMutation) SetCreateTime(t time.Time) { + m.create_time = &t +} + +// CreateTime returns the value of the "create_time" field in the mutation. +func (m *AdminMutation) CreateTime() (r time.Time, exists bool) { + v := m.create_time + if v == nil { + return + } + return *v, true +} + +// OldCreateTime returns the old "create_time" field's value of the Admin entity. +// If the Admin 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 *AdminMutation) OldCreateTime(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreateTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreateTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreateTime: %w", err) + } + return oldValue.CreateTime, nil +} + +// ResetCreateTime resets all changes to the "create_time" field. +func (m *AdminMutation) ResetCreateTime() { + m.create_time = nil +} + +// SetUpdateTime sets the "update_time" field. +func (m *AdminMutation) SetUpdateTime(t time.Time) { + m.update_time = &t +} + +// UpdateTime returns the value of the "update_time" field in the mutation. +func (m *AdminMutation) UpdateTime() (r time.Time, exists bool) { + v := m.update_time + if v == nil { + return + } + return *v, true +} + +// OldUpdateTime returns the old "update_time" field's value of the Admin entity. +// If the Admin 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 *AdminMutation) OldUpdateTime(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdateTime is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdateTime requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdateTime: %w", err) + } + return oldValue.UpdateTime, nil +} + +// ResetUpdateTime resets all changes to the "update_time" field. +func (m *AdminMutation) ResetUpdateTime() { + m.update_time = nil +} + +// Where appends a list predicates to the AdminMutation builder. +func (m *AdminMutation) Where(ps ...predicate.Admin) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AdminMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AdminMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Admin, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AdminMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AdminMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Admin). +func (m *AdminMutation) 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 *AdminMutation) Fields() []string { + fields := make([]string, 0, 7) + if m.name != nil { + fields = append(fields, admin.FieldName) + } + if m.email != nil { + fields = append(fields, admin.FieldEmail) + } + if m.avatar != nil { + fields = append(fields, admin.FieldAvatar) + } + if m.access != nil { + fields = append(fields, admin.FieldAccess) + } + if m.password != nil { + fields = append(fields, admin.FieldPassword) + } + if m.create_time != nil { + fields = append(fields, admin.FieldCreateTime) + } + if m.update_time != nil { + fields = append(fields, admin.FieldUpdateTime) + } + 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 *AdminMutation) Field(name string) (ent.Value, bool) { + switch name { + case admin.FieldName: + return m.Name() + case admin.FieldEmail: + return m.Email() + case admin.FieldAvatar: + return m.Avatar() + case admin.FieldAccess: + return m.Access() + case admin.FieldPassword: + return m.Password() + case admin.FieldCreateTime: + return m.CreateTime() + case admin.FieldUpdateTime: + return m.UpdateTime() + } + 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 *AdminMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case admin.FieldName: + return m.OldName(ctx) + case admin.FieldEmail: + return m.OldEmail(ctx) + case admin.FieldAvatar: + return m.OldAvatar(ctx) + case admin.FieldAccess: + return m.OldAccess(ctx) + case admin.FieldPassword: + return m.OldPassword(ctx) + case admin.FieldCreateTime: + return m.OldCreateTime(ctx) + case admin.FieldUpdateTime: + return m.OldUpdateTime(ctx) + } + return nil, fmt.Errorf("unknown Admin 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 *AdminMutation) SetField(name string, value ent.Value) error { + switch name { + case admin.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case admin.FieldEmail: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEmail(v) + return nil + case admin.FieldAvatar: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAvatar(v) + return nil + case admin.FieldAccess: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAccess(v) + return nil + case admin.FieldPassword: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPassword(v) + return nil + case admin.FieldCreateTime: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreateTime(v) + return nil + case admin.FieldUpdateTime: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdateTime(v) + return nil + } + return fmt.Errorf("unknown Admin field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AdminMutation) 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 *AdminMutation) 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 *AdminMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown Admin numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AdminMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AdminMutation) 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 *AdminMutation) ClearField(name string) error { + return fmt.Errorf("unknown Admin 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 *AdminMutation) ResetField(name string) error { + switch name { + case admin.FieldName: + m.ResetName() + return nil + case admin.FieldEmail: + m.ResetEmail() + return nil + case admin.FieldAvatar: + m.ResetAvatar() + return nil + case admin.FieldAccess: + m.ResetAccess() + return nil + case admin.FieldPassword: + m.ResetPassword() + return nil + case admin.FieldCreateTime: + m.ResetCreateTime() + return nil + case admin.FieldUpdateTime: + m.ResetUpdateTime() + return nil + } + return fmt.Errorf("unknown Admin field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AdminMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AdminMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AdminMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AdminMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AdminMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AdminMutation) EdgeCleared(name string) bool { + 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 *AdminMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown Admin 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 *AdminMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown Admin edge %s", name) +} diff --git a/internal/data/ent/predicate/predicate.go b/internal/data/ent/predicate/predicate.go new file mode 100644 index 0000000..1c3f480 --- /dev/null +++ b/internal/data/ent/predicate/predicate.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package predicate + +import ( + "entgo.io/ent/dialect/sql" +) + +// Admin is the predicate function for admin builders. +type Admin func(*sql.Selector) diff --git a/internal/data/ent/runtime.go b/internal/data/ent/runtime.go new file mode 100644 index 0000000..82d5dc5 --- /dev/null +++ b/internal/data/ent/runtime.go @@ -0,0 +1,48 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "time" + + "kra/internal/data/ent/admin" + "kra/internal/data/ent/schema" +) + +// 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() { + adminFields := schema.Admin{}.Fields() + _ = adminFields + // adminDescName is the schema descriptor for name field. + adminDescName := adminFields[1].Descriptor() + // admin.DefaultName holds the default value on creation for the name field. + admin.DefaultName = adminDescName.Default.(string) + // adminDescEmail is the schema descriptor for email field. + adminDescEmail := adminFields[2].Descriptor() + // admin.DefaultEmail holds the default value on creation for the email field. + admin.DefaultEmail = adminDescEmail.Default.(string) + // adminDescAvatar is the schema descriptor for avatar field. + adminDescAvatar := adminFields[3].Descriptor() + // admin.DefaultAvatar holds the default value on creation for the avatar field. + admin.DefaultAvatar = adminDescAvatar.Default.(string) + // adminDescAccess is the schema descriptor for access field. + adminDescAccess := adminFields[4].Descriptor() + // admin.DefaultAccess holds the default value on creation for the access field. + admin.DefaultAccess = adminDescAccess.Default.(string) + // adminDescPassword is the schema descriptor for password field. + adminDescPassword := adminFields[5].Descriptor() + // admin.DefaultPassword holds the default value on creation for the password field. + admin.DefaultPassword = adminDescPassword.Default.(string) + // adminDescCreateTime is the schema descriptor for create_time field. + adminDescCreateTime := adminFields[6].Descriptor() + // admin.DefaultCreateTime holds the default value on creation for the create_time field. + admin.DefaultCreateTime = adminDescCreateTime.Default.(func() time.Time) + // adminDescUpdateTime is the schema descriptor for update_time field. + adminDescUpdateTime := adminFields[7].Descriptor() + // admin.DefaultUpdateTime holds the default value on creation for the update_time field. + admin.DefaultUpdateTime = adminDescUpdateTime.Default.(func() time.Time) + // admin.UpdateDefaultUpdateTime holds the default value on update for the update_time field. + admin.UpdateDefaultUpdateTime = adminDescUpdateTime.UpdateDefault.(func() time.Time) +} diff --git a/internal/data/ent/runtime/runtime.go b/internal/data/ent/runtime/runtime.go new file mode 100644 index 0000000..3f91d65 --- /dev/null +++ b/internal/data/ent/runtime/runtime.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package runtime + +// The schema-stitching logic is generated in kra/internal/data/ent/runtime.go + +const ( + Version = "v0.14.5" // Version of ent codegen. + Sum = "h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=" // Sum of ent codegen. +) diff --git a/internal/data/ent/schema/admin.go b/internal/data/ent/schema/admin.go new file mode 100644 index 0000000..ad07e95 --- /dev/null +++ b/internal/data/ent/schema/admin.go @@ -0,0 +1,27 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/schema/field" +) + +// Admin holds the schema definition for the Admin entity. +type Admin struct { + ent.Schema +} + +// Fields of the Admin. +func (Admin) Fields() []ent.Field { + return []ent.Field{ + field.Int64("id").Unique().Immutable(), + field.String("name").Default(""), + field.String("email").Default(""), + field.String("avatar").Default(""), + field.String("access").Default(""), + field.String("password").Default(""), + field.Time("create_time").Default(time.Now).Immutable(), + field.Time("update_time").Default(time.Now).UpdateDefault(time.Now), + } +} diff --git a/internal/data/ent/tx.go b/internal/data/ent/tx.go new file mode 100644 index 0000000..2789ff3 --- /dev/null +++ b/internal/data/ent/tx.go @@ -0,0 +1,236 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + stdsql "database/sql" + "fmt" + "sync" + + "entgo.io/ent/dialect" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // Admin is the client for interacting with the Admin builders. + Admin *AdminClient + + // 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.Admin = NewAdminClient(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: Admin.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) + +// ExecContext allows calling the underlying ExecContext method of the transaction if it is supported by it. +// See, database/sql#Tx.ExecContext for more information. +func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) { + ex, ok := tx.tx.(interface { + ExecContext(context.Context, string, ...any) (stdsql.Result, error) + }) + if !ok { + return nil, fmt.Errorf("Tx.ExecContext is not supported") + } + return ex.ExecContext(ctx, query, args...) +} + +// QueryContext allows calling the underlying QueryContext method of the transaction if it is supported by it. +// See, database/sql#Tx.QueryContext for more information. +func (tx *txDriver) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) { + q, ok := tx.tx.(interface { + QueryContext(context.Context, string, ...any) (*stdsql.Rows, error) + }) + if !ok { + return nil, fmt.Errorf("Tx.QueryContext is not supported") + } + return q.QueryContext(ctx, query, args...) +} diff --git a/internal/server/grpc.go b/internal/server/grpc.go new file mode 100644 index 0000000..52dec8d --- /dev/null +++ b/internal/server/grpc.go @@ -0,0 +1,33 @@ +package server + +import ( + v1 "kra/api/kratos/admin/v1" + "kra/internal/conf" + "kra/internal/service" + "kra/pkg/validate" + + "github.com/go-kratos/kratos/v3/middleware/recovery" + "github.com/go-kratos/kratos/v3/transport/grpc" +) + +// NewGRPCServer new a gRPC server. +func NewGRPCServer(c *conf.Server, admin *service.AdminService) *grpc.Server { + var opts = []grpc.ServerOption{ + grpc.Middleware( + recovery.Recovery(), + validate.Middleware(), + ), + } + if c.Grpc.Network != "" { + opts = append(opts, grpc.Network(c.Grpc.Network)) + } + if c.Grpc.Addr != "" { + opts = append(opts, grpc.Address(c.Grpc.Addr)) + } + if c.Grpc.Timeout != nil { + opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration())) + } + srv := grpc.NewServer(opts...) + v1.RegisterAdminServiceServer(srv, admin) + return srv +} diff --git a/internal/server/http.go b/internal/server/http.go new file mode 100644 index 0000000..6975c9c --- /dev/null +++ b/internal/server/http.go @@ -0,0 +1,37 @@ +package server + +import ( + v1 "kra/api/kratos/admin/v1" + "kra/internal/conf" + "kra/internal/service" + "kra/pkg/auth" + "kra/pkg/validate" + + "github.com/go-kratos/kratos/v3/middleware/recovery" + "github.com/go-kratos/kratos/v3/transport/http" +) + +// NewHTTPServer new an HTTP server. +func NewHTTPServer(c *conf.Server, admin *service.AdminService) *http.Server { + var opts = []http.ServerOption{ + http.Filter( + auth.Middleware(), + ), + http.Middleware( + recovery.Recovery(), + validate.Middleware(), + ), + } + if c.Http.Network != "" { + opts = append(opts, http.Network(c.Http.Network)) + } + if c.Http.Addr != "" { + opts = append(opts, http.Address(c.Http.Addr)) + } + if c.Http.Timeout != nil { + opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration())) + } + srv := http.NewServer(opts...) + v1.RegisterAdminServiceHTTPServer(srv, admin) + return srv +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..f389425 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,8 @@ +package server + +import ( + "github.com/google/wire" +) + +// ProviderSet is server providers. +var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer) diff --git a/internal/service/README.md b/internal/service/README.md new file mode 100644 index 0000000..42321b7 --- /dev/null +++ b/internal/service/README.md @@ -0,0 +1 @@ +# Service diff --git a/internal/service/admin.go b/internal/service/admin.go new file mode 100644 index 0000000..9268227 --- /dev/null +++ b/internal/service/admin.go @@ -0,0 +1,232 @@ +package service + +import ( + "context" + "time" + + v1 "kra/api/kratos/admin/v1" + "kra/internal/biz" + "kra/pkg/auth" + "github.com/go-kratos/kratos/v3/errors" + + "go.einride.tech/aip/fieldmask" + "go.einride.tech/aip/filtering" + "go.einride.tech/aip/ordering" + "go.einride.tech/aip/pagination" + "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func convertAdmin(m *biz.Admin) *v1.Admin { + return &v1.Admin{ + Id: m.ID, + Name: m.Name, + Email: m.Email, + Avatar: m.Avatar, + Access: m.Access, + CreateTime: timestamppb.New(m.CreateTime), + UpdateTime: timestamppb.New(m.UpdateTime), + } +} + +// AdminService is a greeter service. +type AdminService struct { + v1.UnimplementedAdminServiceServer + + uc *biz.AdminUsecase +} + +// NewAdminService new a greeter service. +func NewAdminService(uc *biz.AdminUsecase) *AdminService { + return &AdminService{uc: uc} +} + +// Current implements auth current admin retrieval. +func (s *AdminService) Current(ctx context.Context, req *emptypb.Empty) (*v1.Admin, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + admin, err := s.uc.GetAdmin(ctx, a.UserID) + if err != nil { + return nil, err + } + return convertAdmin(admin), nil +} + +// Login implements auth login. +func (s *AdminService) Login(ctx context.Context, req *v1.LoginRequest) (*v1.Admin, error) { + var ( + err error + admin *biz.Admin + ) + switch v := req.Identity.(type) { + case *v1.LoginRequest_Username: + admin, err = s.uc.LoginByUsername(ctx, v.Username, req.Password) + if err != nil { + return nil, err + } + case *v1.LoginRequest_Email: + admin, err = s.uc.LoginByEmail(ctx, v.Email, req.Password) + if err != nil { + return nil, err + } + default: + return nil, errors.BadRequest(v1.ErrorReason_INVALID_IDENTITY_TYPE.String(), "unsupported identity type") + } + if err := auth.SetCookie(ctx, admin.ID, admin.Access, time.Now().Add(7*24*time.Hour)); err != nil { + return nil, err + } + return convertAdmin(admin), nil +} + +// Logout implements auth logout. +func (s *AdminService) Logout(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + if err := s.uc.Logout(ctx, a.UserID); err != nil { + return nil, err + } + if err := auth.DeleteCookie(ctx); err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +// CreateAdmin implements admin creation. +func (s *AdminService) CreateAdmin(ctx context.Context, req *v1.CreateAdminRequest) (*v1.Admin, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + if !a.HasAdminAccess() { + return nil, auth.ErrPermissionDenied + } + admin, err := s.uc.CreateAdmin(ctx, &biz.Admin{ + Name: req.Admin.Name, + Email: req.Admin.Email, + Password: req.Admin.Password, + Avatar: req.Admin.Avatar, + Access: req.Admin.Access, + }) + if err != nil { + return nil, err + } + return convertAdmin(admin), nil +} + +// UpdateAdmin implements admin update. +func (s *AdminService) UpdateAdmin(ctx context.Context, req *v1.UpdateAdminRequest) (*v1.Admin, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + if !a.HasAdminAccess() { + return nil, auth.ErrPermissionDenied + } + admin, err := s.GetAdmin(ctx, &v1.GetAdminRequest{Id: req.Admin.Id}) + if err != nil { + return nil, err + } + fieldmask.Update(req.UpdateMask, admin, req.Admin) + updated, err := s.uc.UpdateAdmin(ctx, &biz.Admin{ + ID: admin.Id, + Name: admin.Name, + Email: admin.Email, + Password: admin.Password, + Avatar: admin.Avatar, + Access: admin.Access, + }) + if err != nil { + return nil, err + } + return convertAdmin(updated), nil +} + +// DeleteAdmin implements admin deletion. +func (s *AdminService) DeleteAdmin(ctx context.Context, req *v1.DeleteAdminRequest) (*emptypb.Empty, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + if !a.HasAdminAccess() { + return nil, auth.ErrPermissionDenied + } + if err := s.uc.DeleteAdmin(ctx, req.Id); err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +// GetAdmin implements admin retrieval. +func (s *AdminService) GetAdmin(ctx context.Context, req *v1.GetAdminRequest) (*v1.Admin, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + if !a.HasAdminAccess() { + return nil, auth.ErrPermissionDenied + } + admin, err := s.uc.GetAdmin(ctx, req.Id) + if err != nil { + return nil, err + } + return convertAdmin(admin), nil +} + +// ListAdmins implements admin listing with filtering, ordering, and pagination. +func (s *AdminService) ListAdmins(ctx context.Context, req *v1.ListAdminsRequest) (*v1.AdminSet, error) { + a, ok := auth.FromContext(ctx) + if !ok { + return nil, auth.ErrUnauthenticated + } + if !a.HasAdminAccess() { + return nil, auth.ErrPermissionDenied + } + declarations, err := filtering.NewDeclarations( + filtering.DeclareStandardFunctions(), + filtering.DeclareIdent("name", filtering.TypeString), + filtering.DeclareIdent("email", filtering.TypeString), + filtering.DeclareIdent("phone", filtering.TypeString), + filtering.DeclareIdent("create_time", filtering.TypeTimestamp), + ) + if err != nil { + return nil, err + } + filter, err := filtering.ParseFilter(req, declarations) + if err != nil { + return nil, err + } + pageToken, err := pagination.ParsePageToken(req) + if err != nil { + return nil, err + } + orderBy, err := ordering.ParseOrderBy(req) + if err != nil { + return nil, err + } + if req.PageSize <= 0 { + req.PageSize = 20 + } + admins, err := s.uc.ListAdmins(ctx, + biz.ListFilter(filter), + biz.ListOrderBy(orderBy), + biz.ListLimit(int(req.PageSize)), + biz.ListOffset(int(pageToken.Offset)), + ) + if err != nil { + return nil, err + } + adminSet := &v1.AdminSet{ + Admins: make([]*v1.Admin, 0, len(admins)), + } + if len(admins) >= int(req.PageSize) { + adminSet.NextPageToken = pageToken.Next(req).String() + } + for _, admin := range admins { + adminSet.Admins = append(adminSet.Admins, convertAdmin(admin)) + } + return adminSet, nil +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..d4313fb --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,6 @@ +package service + +import "github.com/google/wire" + +// ProviderSet is service providers. +var ProviderSet = wire.NewSet(NewAdminService) diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..520190f --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,249 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: AdminService API + description: AdminService is the admin service definition. + version: 0.0.1 +paths: + /v1/admins/create: + post: + tags: + - AdminService + description: CreateAdmin creates a new admin. + operationId: AdminService_CreateAdmin + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + /v1/admins/current: + get: + tags: + - AdminService + description: Current returns the currently logged-in user. + operationId: AdminService_Current + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + /v1/admins/list: + get: + tags: + - AdminService + description: ListAdmins returns a list of admins. + operationId: AdminService_ListAdmins + parameters: + - name: pageSize + in: query + description: Optional. The number of admins per page. + schema: + type: integer + format: int32 + - name: pageToken + in: query + description: Optional. The page token. + schema: + type: string + - name: filter + in: query + description: |- + Optional. The standard list filter. + Supported fields: + * `name` (i.e. `name="John Doe"`) + * `email` (i.e. `email="admin@go-kratos.dev"`) + * `phone` (i.e. `phone="+1234567890"`) + * `create_time` range (i.e. `timestamp>="2025-01-31T11:30:00-04:00"` where + the timestamp is in RFC 3339 format) + + More detail in [AIP-160](https://google.aip.dev/160). + schema: + type: string + - name: orderBy + in: query + description: |- + Optional. A comma-separated list of fields to order by, sorted in ascending + order. Use "desc" after a field name for descending. Supported fields: + - `create_time` + + Example: `create_time desc`. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.AdminSet' + /v1/admins/login: + post: + tags: + - AdminService + description: Login a user and return the username. + operationId: AdminService_Login + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.LoginRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + /v1/admins/logout: + post: + tags: + - AdminService + description: Logout the currently logged-in user. + operationId: AdminService_Logout + requestBody: + content: + application/json: {} + required: true + responses: + "200": + description: OK + content: {} + /v1/admins/update: + put: + tags: + - AdminService + description: UpdateAdmin updates an existing admin. + operationId: AdminService_UpdateAdmin + parameters: + - name: updateMask + in: query + description: Required. Mask of fields to update. + schema: + type: string + format: field-mask + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + /v1/admins/{id}: + get: + tags: + - AdminService + description: GetAdmin retrieves an admin by ID. + operationId: AdminService_GetAdmin + parameters: + - name: id + in: path + description: The ID of the admin to retrieve. + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + delete: + tags: + - AdminService + description: DeleteAdmin deletes an admin by ID. + operationId: AdminService_DeleteAdmin + parameters: + - name: id + in: path + description: Required. The ID of the admin to delete. + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} +components: + schemas: + kratos.admin.v1.Admin: + type: object + properties: + id: + type: string + description: The unique ID of the user. + name: + type: string + description: The name of the user. + email: + type: string + description: The email of the user. + phone: + type: string + description: The phone number of the user. + avatar: + type: string + description: The avatar URL of the user. + access: + type: string + description: |- + The access level of the user. + Possible values are: "admin", "user", etc. + password: + type: string + description: The password of the user. + createTime: + type: string + description: The timestamp at which the user was created. + format: date-time + updateTime: + type: string + description: The latest timestamp at which the user was updated. + format: date-time + description: Admin is the admin message. + kratos.admin.v1.AdminSet: + type: object + properties: + admins: + type: array + items: + $ref: '#/components/schemas/kratos.admin.v1.Admin' + description: The set of admins. + nextPageToken: + type: string + description: The next page token. + description: AdminSet is the set of admins. + kratos.admin.v1.LoginRequest: + required: + - password + type: object + properties: + password: + type: string + description: Required. Password of the user. + username: + type: string + email: + type: string + description: LoginRequest is the request message for the Login method. +tags: + - name: AdminService diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 0000000..0d51dde --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,32 @@ +package auth + +import ( + "context" + + "github.com/golang-jwt/jwt/v5" +) + +// Auth user auth. +type Auth struct { + UserID int64 `json:"id"` + Access string `json:"access"` + jwt.RegisteredClaims +} + +// HasAdminAccess checks if the user has admin access. +func (a *Auth) HasAdminAccess() bool { + return a.Access == "admin" +} + +type authKey struct{} + +// NewContext returns a new Context that carries value. +func NewContext(ctx context.Context, auth *Auth) context.Context { + return context.WithValue(ctx, authKey{}, auth) +} + +// FromContext returns the Auth value stored in ctx, if any. +func FromContext(ctx context.Context) (auth *Auth, ok bool) { + auth, ok = ctx.Value(authKey{}).(*Auth) + return +} diff --git a/pkg/auth/config.go b/pkg/auth/config.go new file mode 100644 index 0000000..1985eab --- /dev/null +++ b/pkg/auth/config.go @@ -0,0 +1,22 @@ +package auth + +import ( + "os" + "time" +) + +// authSecretFromEnv retrieves the authentication secret from the environment variable. +func authSecretFromEnv(key string) string { + if secret := os.Getenv(key); secret != "" { + return secret + } + return time.Now().Format("20060102150405") +} + +// cookieNameFromEnv retrieves the cookie name from the environment variable. +func cookieNameFromEnv(key string) string { + if name := os.Getenv(key); name != "" { + return name + } + return "access_token" +} diff --git a/pkg/auth/middleware.go b/pkg/auth/middleware.go new file mode 100644 index 0000000..14b88b9 --- /dev/null +++ b/pkg/auth/middleware.go @@ -0,0 +1,100 @@ +package auth + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/go-kratos/kratos/v3/errors" + "github.com/go-kratos/kratos/v3/transport" + httpm "github.com/go-kratos/kratos/v3/transport/http" +) + +// Error reasons reported by this middleware. They mirror the wire-visible names +// in api///error_reason.proto, but are declared here so `pkg` +// does not depend on a domain's generated proto. TestReasonsMatchAPIEnum pins +// the two together so they cannot drift. +const ( + reasonUnauthenticated = "UNAUTHENTICATED" + reasonPermissionDenied = "PERMISSION_DENIED" +) + +var ( + // noAuthPaths defines the paths that do not require authentication. + noAuthPaths = map[string]struct{}{ + "/v1/admins/login": {}, + } + // authSecretKey is the secret key used for signing JWT tokens. + authSecretKey = authSecretFromEnv("KRATOS_AUTH_SECRET") + // cookieName is the name of the cookie that stores the authorization token. + cookieName = cookieNameFromEnv("KRATOS_AUTH_COOKIE") + // ErrUnauthenticated indicates that the request carried no usable credential. + ErrUnauthenticated = errors.Unauthorized(reasonUnauthenticated, "Token is invalid") + // ErrPermissionDenied indicates that the caller lacks the required access. + ErrPermissionDenied = errors.Forbidden(reasonPermissionDenied, "Access denied") +) + +// Middleware is an authentication middleware for HTTP servers. +func Middleware() httpm.FilterFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if _, ok := noAuthPaths[r.URL.Path]; ok { + next.ServeHTTP(w, r) + return + } + // This filter runs outside the kratos handler chain, so the error + // encoder has to be invoked explicitly. Writing plain text here + // instead would leave clients unable to read code / reason. + cookie, err := r.Cookie(cookieName) + if err != nil { + httpm.DefaultErrorEncoder(w, r, ErrUnauthenticated) + return + } + auth, err := ParseToken(cookie.Value, authSecretKey) + if err != nil { + httpm.DefaultErrorEncoder(w, r, err) + return + } + ctx := NewContext(r.Context(), auth) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + +// SetCookie sets the login cookie in the HTTP response. +func SetCookie(ctx context.Context, userID int64, access string, expiresAt time.Time) error { + tr, ok := transport.FromServerContext(ctx) + if !ok { + return fmt.Errorf("failed to get transport from context") + } + token, err := GenerateToken(userID, access, authSecretKey, expiresAt) + if err != nil { + return err + } + cookie := &http.Cookie{ + Name: cookieName, + Value: token, + Path: "/", + Expires: expiresAt, + } + tr.ReplyHeader().Add("Set-Cookie", cookie.String()) + return nil +} + +// DeleteCookie clears the login cookie in the HTTP response. +func DeleteCookie(ctx context.Context) error { + tr, ok := transport.FromServerContext(ctx) + if !ok { + return fmt.Errorf("failed to get transport from context") + } + expires := time.Now().AddDate(0, 0, -1) + cookie := &http.Cookie{ + Name: cookieName, + Value: "", + Path: "/", + Expires: expires, + } + tr.ReplyHeader().Add("Set-Cookie", cookie.String()) + return nil +} diff --git a/pkg/auth/middleware_test.go b/pkg/auth/middleware_test.go new file mode 100644 index 0000000..bc32063 --- /dev/null +++ b/pkg/auth/middleware_test.go @@ -0,0 +1,125 @@ +package auth + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + v1 "kra/api/kratos/admin/v1" +) + +// errorBody mirrors the wire shape of kratos errors.Status. +type errorBody struct { + Code int32 `json:"code"` + Reason string `json:"reason"` + Message string `json:"message"` +} + +// serve runs the middleware over a handler that records whether it was reached. +func serve(t *testing.T, req *http.Request) (*httptest.ResponseRecorder, bool) { + t.Helper() + reached := false + handler := Middleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + reached = true + })) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + return rec, reached +} + +// The middleware runs outside the kratos handler chain, so it has to invoke the +// error encoder itself. These tests pin the body down as structured protojson: +// writing plain text here would leave clients unable to read code / reason. +func TestMiddlewareRejectsMissingCookie(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/v1/admins/current", nil) + req.Header.Set("Accept", "application/protojson") + + rec, reached := serve(t, req) + + if reached { + t.Fatal("handler was reached without a credential") + } + if rec.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized) + } + if ct := rec.Header().Get("Content-Type"); ct != "application/protojson" { + t.Fatalf("Content-Type = %q, want application/protojson", ct) + } + var body errorBody + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("body is not JSON (%v): %s", err, rec.Body.String()) + } + if body.Code != http.StatusUnauthorized { + t.Errorf("body code = %d, want %d", body.Code, http.StatusUnauthorized) + } + if want := v1.ErrorReason_UNAUTHENTICATED.String(); body.Reason != want { + t.Errorf("body reason = %q, want %q", body.Reason, want) + } +} + +func TestMiddlewareRejectsInvalidToken(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/v1/admins/current", nil) + req.Header.Set("Accept", "application/protojson") + req.AddCookie(&http.Cookie{Name: cookieName, Value: "not-a-jwt"}) + + rec, reached := serve(t, req) + + if reached { + t.Fatal("handler was reached with an invalid token") + } + if rec.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized) + } + if ct := rec.Header().Get("Content-Type"); ct != "application/protojson" { + t.Fatalf("Content-Type = %q, want application/protojson", ct) + } + var body errorBody + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("body is not JSON (%v): %s", err, rec.Body.String()) + } + if body.Code != http.StatusUnauthorized { + t.Errorf("body code = %d, want %d", body.Code, http.StatusUnauthorized) + } + if want := v1.ErrorReason_UNAUTHENTICATED.String(); body.Reason != want { + t.Errorf("body reason = %q, want %q", body.Reason, want) + } +} + +// The reason constants in this package are declared locally so `pkg` stays free +// of any domain's generated proto. That freedom is only safe if the two stay in +// step, which is what this test enforces. +func TestReasonsMatchAPIEnum(t *testing.T) { + if want := v1.ErrorReason_UNAUTHENTICATED.String(); reasonUnauthenticated != want { + t.Errorf("reasonUnauthenticated = %q, want %q", reasonUnauthenticated, want) + } + if want := v1.ErrorReason_PERMISSION_DENIED.String(); reasonPermissionDenied != want { + t.Errorf("reasonPermissionDenied = %q, want %q", reasonPermissionDenied, want) + } +} + +func TestMiddlewareAllowsValidToken(t *testing.T) { + token, err := GenerateToken(42, "admin", authSecretKey, time.Now().Add(time.Hour)) + if err != nil { + t.Fatalf("GenerateToken: %v", err) + } + req := httptest.NewRequest(http.MethodGet, "/v1/admins/current", nil) + req.AddCookie(&http.Cookie{Name: cookieName, Value: token}) + + rec, reached := serve(t, req) + + if !reached { + t.Fatalf("handler was not reached, status = %d body = %s", rec.Code, rec.Body.String()) + } +} + +func TestMiddlewareSkipsNoAuthPaths(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/v1/admins/login", nil) + + _, reached := serve(t, req) + + if !reached { + t.Fatal("login path should not require authentication") + } +} diff --git a/pkg/auth/token.go b/pkg/auth/token.go new file mode 100644 index 0000000..4d793e4 --- /dev/null +++ b/pkg/auth/token.go @@ -0,0 +1,48 @@ +package auth + +import ( + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" +) + +// GenerateToken generates a JWT token for the given username. +func GenerateToken(userID int64, access, secret string, expiresAt time.Time) (string, error) { + now := time.Now() + claims := Auth{ + UserID: userID, + Access: access, + RegisteredClaims: jwt.RegisteredClaims{ + ID: uuid.NewString(), + Issuer: "kratos", + Subject: "user", + Audience: []string{"admin"}, + IssuedAt: jwt.NewNumericDate(now), + NotBefore: jwt.NewNumericDate(now), + ExpiresAt: jwt.NewNumericDate(expiresAt), + }, + } + return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret)) +} + +// ParseToken parses the JWT token string and returns the Auth claims. +func ParseToken(tokenStr, secret string) (*Auth, error) { + token, err := jwt.ParseWithClaims(tokenStr, &Auth{}, func(token *jwt.Token) (interface{}, error) { + return []byte(secret), nil + }) + if err != nil { + // Map the jwt driver error to a typed one; returning it raw would make + // callers see a 500 for a merely expired or malformed token. The cause + // is kept so logs can still tell expiry from a bad signature. + return nil, ErrUnauthenticated.WithCause(err) + } + if !token.Valid { + return nil, ErrUnauthenticated + } + auth, ok := token.Claims.(*Auth) + if !ok { + return nil, ErrUnauthenticated + } + return auth, nil +} diff --git a/pkg/validate/validate.go b/pkg/validate/validate.go new file mode 100644 index 0000000..37f8b4e --- /dev/null +++ b/pkg/validate/validate.go @@ -0,0 +1,24 @@ +package validate + +import ( + "context" + + "github.com/go-kratos/kratos/v3/errors" + "github.com/go-kratos/kratos/v3/middleware" + "go.einride.tech/aip/fieldbehavior" + "google.golang.org/protobuf/proto" +) + +// Middleware is a middleware that validates the request message with [FieldBehavior](https://google.aip.dev/203) +func Middleware() middleware.Middleware { + return func(handler middleware.Handler) middleware.Handler { + return func(ctx context.Context, req any) (reply any, err error) { + if msg, ok := req.(proto.Message); ok { + if err := fieldbehavior.ValidateRequiredFields(msg); err != nil { + return nil, errors.BadRequest("VALIDATOR", err.Error()).WithCause(err) + } + } + return handler(ctx, req) + } + } +} diff --git a/web/.commitlintrc.js b/web/.commitlintrc.js new file mode 100644 index 0000000..422b194 --- /dev/null +++ b/web/.commitlintrc.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] }; diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 0000000..7e3649a --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..7237b34 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +**/node_modules +# roadhog-api-doc ignore +/src/utils/request-temp.js +_roadhog-api-doc + +# production +/dist + +# misc +.DS_Store +npm-debug.log* +yarn-error.log + +/coverage +.idea +yarn.lock +package-lock.json +*bak +.vscode + + +# visual studio code +.history +*.log +functions/* +.temp/** + +# umi +.umi +.umi-production +.umi-test +.turbopack + +# screenshot +screenshot +.firebase + +build diff --git a/web/.husky/commit-msg b/web/.husky/commit-msg new file mode 100644 index 0000000..0a4b97d --- /dev/null +++ b/web/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 diff --git a/web/.husky/pre-commit b/web/.husky/pre-commit new file mode 100644 index 0000000..c27d889 --- /dev/null +++ b/web/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged diff --git a/web/.lintstagedrc b/web/.lintstagedrc new file mode 100644 index 0000000..1c2023c --- /dev/null +++ b/web/.lintstagedrc @@ -0,0 +1,5 @@ +{ + "**/*.{js,jsx,tsx,ts,md,css,less,json}": [ + "npx @biomejs/biome check --write" + ] +} diff --git a/web/.npmrc b/web/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/web/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/web/LICENSE b/web/LICENSE new file mode 100644 index 0000000..b65958e --- /dev/null +++ b/web/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-present Alipay.inc + +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/web/biome.json b/web/biome.json new file mode 100644 index 0000000..3acf0db --- /dev/null +++ b/web/biome.json @@ -0,0 +1,49 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "ignoreUnknown": true, + "includes": [ + "**/*", + "!**/.umi", + "!**/.umi-production", + "!**/.umi-test", + "!**/.umi-test-production", + "!**/src/services", + "!**/mock", + "!**/dist", + "!**/server", + "!**/public", + "!**/coverage", + "!**/node_modules", + "!biome.json" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off" + }, + "correctness": { + "useUniqueElementIds": "off", + "useExhaustiveDependencies": "off" + }, + "a11y": { + "noStaticElementInteractions": "off", + "useValidAnchor": "off", + "useKeyWithClickEvents": "off" + } + } + }, + "javascript": { + "jsxRuntime": "reactClassic", + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/web/config/config.ts b/web/config/config.ts new file mode 100644 index 0000000..3228b74 --- /dev/null +++ b/web/config/config.ts @@ -0,0 +1,157 @@ +// https://umijs.org/config/ + +import { defineConfig } from "@umijs/max"; +import { join } from "node:path"; +import defaultSettings from "./defaultSettings"; +import proxy from "./proxy"; + +import routes from "./routes"; + +const { UMI_ENV = "dev" } = process.env; + +/** + * @name 使用公共路径 + * @description 部署时的路径,如果部署在非根目录下,需要配置这个变量 + * @doc https://umijs.org/docs/api/config#publicpath + */ +const PUBLIC_PATH: string = "/"; + +export default defineConfig({ + /** + * @name 开启 hash 模式 + * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。 + * @doc https://umijs.org/docs/api/config#hash + */ + hash: true, + + publicPath: PUBLIC_PATH, + + /** + * @name 兼容性设置 + * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖 + * @doc https://umijs.org/docs/api/config#targets + */ + // targets: { + // ie: 11, + // }, + /** + * @name 路由的配置,不在路由中引入的文件不会编译 + * @description 只支持 path,component,routes,redirect,wrappers,title 的配置 + * @doc https://umijs.org/docs/guides/routes + */ + // umi routes: https://umijs.org/docs/routing + routes, + /** + * @name 主题的配置 + * @description 虽然叫主题,但是其实只是 less 的变量设置 + * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn + * @doc umi 的 theme 配置 https://umijs.org/docs/api/config#theme + */ + // theme: { '@primary-color': '#1DA57A' } + /** + * @name moment 的国际化配置 + * @description 如果对国际化没有要求,打开之后能减少js的包大小 + * @doc https://umijs.org/docs/api/config#ignoremomentlocale + */ + ignoreMomentLocale: true, + /** + * @name 代理配置 + * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了 + * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。 + * @doc 代理介绍 https://umijs.org/docs/guides/proxy + * @doc 代理配置 https://umijs.org/docs/api/config#proxy + */ + proxy: proxy[UMI_ENV as keyof typeof proxy], + /** + * @name 快速热更新配置 + * @description 一个不错的热更新组件,更新时可以保留 state + */ + fastRefresh: true, + //============== 以下都是max的插件配置 =============== + /** + * @name 数据流插件 + * @@doc https://umijs.org/docs/max/data-flow + */ + model: {}, + /** + * 一个全局的初始数据流,可以用它在插件之间共享数据 + * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。 + * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81 + */ + initialState: {}, + /** + * @name layout 插件 + * @doc https://umijs.org/docs/max/layout-menu + */ + title: "Ant Design Pro", + layout: { + locale: true, + ...defaultSettings, + }, + /** + * @name moment2dayjs 插件 + * @description 将项目中的 moment 替换为 dayjs + * @doc https://umijs.org/docs/max/moment2dayjs + */ + moment2dayjs: { + preset: "antd", + plugins: ["duration"], + }, + /** + * @name 国际化插件 + * @doc https://umijs.org/docs/max/i18n + */ + locale: { + // default zh-CN + default: "zh-CN", + antd: true, + // default true, when it is true, will use `navigator.language` overwrite default + baseNavigator: true, + }, + /** + * @name antd 插件 + * @description 内置了 babel import 插件 + * @doc https://umijs.org/docs/max/antd#antd + */ + antd: { + appConfig: {}, + configProvider: { + theme: { + token: { + fontFamily: "AlibabaSans, sans-serif", + }, + }, + }, + }, + /** + * @name 网络请求配置 + * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 + * @doc https://umijs.org/docs/max/request + */ + request: {}, + /** + * @name 权限插件 + * @description 基于 initialState 的权限插件,必须先打开 initialState + * @doc https://umijs.org/docs/max/access + */ + access: {}, + /** + * @name 中额外的 script + * @description 配置 中额外的 script + */ + headScripts: [ + // 解决首次加载时白屏的问题 + { src: join(PUBLIC_PATH, "scripts/loading.js"), async: true }, + ], + //================ pro 插件配置 ================= + presets: ["umi-presets-pro"], + mock: { + include: ["mock/**/*", "src/pages/**/_mock.ts"], + }, + utoopack: {}, + requestRecord: {}, + exportStatic: {}, + define: { + "process.env.CI": process.env.CI, + }, +}); diff --git a/web/config/defaultSettings.ts b/web/config/defaultSettings.ts new file mode 100644 index 0000000..6710cad --- /dev/null +++ b/web/config/defaultSettings.ts @@ -0,0 +1,28 @@ +import type { ProLayoutProps } from '@ant-design/pro-components'; + +/** + * @name + */ +const Settings: ProLayoutProps & { + pwa?: boolean; + logo?: string; +} = { + navTheme: 'light', + // 拂晓蓝 + colorPrimary: '#1890ff', + layout: 'mix', + contentWidth: 'Fluid', + fixedHeader: false, + fixSiderbar: true, + colorWeak: false, + title: 'Ant Design Pro', + pwa: true, + logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', + iconfontUrl: '', + token: { + // 参见ts声明,demo 见文档,通过token 修改样式 + //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F + }, +}; + +export default Settings; diff --git a/web/config/proxy.ts b/web/config/proxy.ts new file mode 100644 index 0000000..c7ddc88 --- /dev/null +++ b/web/config/proxy.ts @@ -0,0 +1,23 @@ +/** + * @name 代理的配置 + * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 + * ------------------------------- + * The agent cannot take effect in the production environment + * so there is no configuration of the production environment + * For details, please see + * https://pro.ant.design/docs/deploy + * + * @doc https://umijs.org/docs/guides/proxy + */ +export default { + /** + * @name 详细的代理配置 + * @doc https://github.com/chimurai/http-proxy-middleware + */ + dev: { + "/v1/": { + target: "http://localhost:8000", + changeOrigin: true, + }, + }, +}; diff --git a/web/config/routes.ts b/web/config/routes.ts new file mode 100644 index 0000000..c8fbf91 --- /dev/null +++ b/web/config/routes.ts @@ -0,0 +1,47 @@ +/** + * @name umi 的路由配置 + * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置 + * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。 + * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 + * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。 + * @param redirect 配置路由跳转 + * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 + * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 + * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 则取值应为 user 或者 User + * @doc https://umijs.org/docs/guides/routes + */ +export default [ + { + path: "/user", + layout: false, + routes: [ + { + name: "login", + path: "/user/login", + component: "./user/login", + }, + ], + }, + { + path: "/welcome", + name: "welcome", + icon: "smile", + component: "./Welcome", + }, + { + name: "admin", + icon: "crown", + path: "/admins", + access: "canAdmin", + component: "./admins", + }, + { + path: "/", + redirect: "/welcome", + }, + { + component: "404", + layout: false, + path: "./*", + }, +]; diff --git a/web/jest.config.ts b/web/jest.config.ts new file mode 100644 index 0000000..99051c4 --- /dev/null +++ b/web/jest.config.ts @@ -0,0 +1,21 @@ +import { configUmiAlias, createConfig } from '@umijs/max/test'; + +export default async (): Promise => { + const config = await configUmiAlias({ + ...createConfig({ + target: 'browser', + }), + }); + return { + ...config, + testEnvironmentOptions: { + ...(config?.testEnvironmentOptions || {}), + url: 'http://localhost:8000', + }, + setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], + globals: { + ...config.globals, + localStorage: null, + }, + }; +}; diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..51b06f6 --- /dev/null +++ b/web/package.json @@ -0,0 +1,96 @@ +{ + "name": "ant-design-pro", + "version": "6.0.0", + "private": true, + "description": "An out-of-box UI solution for enterprise applications", + "repository": "git@github.com:ant-design/ant-design-pro.git", + "scripts": { + "analyze": "cross-env ANALYZE=1 max build", + "build": "max build", + "deploy": "npm run build && npm run gh-pages", + "dev": "npm run start:dev", + "gh-pages": "gh-pages -d dist", + "i18n-remove": "pro i18n-remove --locale=zh-CN --write", + "postinstall": "max setup", + "jest": "jest", + "lint": "npm run biome:lint && npm run tsc", + "lint-staged": "lint-staged", + "biome:lint": "npx @biomejs/biome lint", + "prepare": "husky", + "preview": "npm run build && max preview --port 8000", + "record": "cross-env NODE_ENV=development UMI_ENV=test max record --scene=login", + "serve": "umi-serve", + "start": "cross-env UMI_ENV=dev max dev", + "start:dev": "cross-env UMI_ENV=dev PORT=3000 MOCK=none max dev", + "start:no-mock": "cross-env MOCK=none max dev", + "start:pre": "cross-env UMI_ENV=pre MOCK=none max dev", + "start:test": "cross-env UMI_ENV=test MOCK=none max dev", + "test": "jest", + "test:coverage": "npm run jest -- --coverage", + "test:update": "npm run jest -- -u", + "tsc": "tsc --noEmit" + }, + "browserslist": ["defaults"], + "dependencies": { + "@ant-design/icons": "^6.2.5", + "@ant-design/pro-components": "^3.1.12-0", + "antd": "^6.4.4", + "antd-style": "^4.1.0", + "classnames": "^2.5.1", + "dayjs": "^1.11.13", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@ant-design/pro-cli": "^3.3.0", + "@commitlint/cli": "^20.1.0", + "@commitlint/config-conventional": "^20.0.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", + "@types/express": "^5.0.3", + "@types/jest": "^30.0.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@types/react-helmet": "^6.1.11", + "@umijs/lint": "^4.6.1", + "@umijs/max": "^4.6.2", + "cross-env": "^10.0.0", + "express": "^5.2.0", + "gh-pages": "^6.1.1", + "husky": "^9.1.7", + "jest": "^30.0.4", + "jest-environment-jsdom": "^30.0.5", + "lint-staged": "^16.1.2", + "mockjs": "^1.1.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.3", + "umi-presets-pro": "^2.0.3", + "umi-serve": "^1.9.11", + "@biomejs/biome": "^2.1.1", + "@types/node": "^24.0.13" + }, + "engines": { + "node": ">=20.0.0" + }, + "create-umi": { + "ignoreScript": ["docker*", "functions*", "site", "generateMock"], + "ignoreDependencies": ["netlify*", "serverless"], + "ignore": [ + ".dockerignore", + ".git", + ".github", + ".gitpod.yml", + "CODE_OF_CONDUCT.md", + "Dockerfile", + "Dockerfile.*", + "lambda", + "LICENSE", + "netlify.toml", + "README.*.md", + "azure-pipelines.yml", + "docker", + "CNAME", + "create-umi" + ] + } +} diff --git a/web/public/CNAME b/web/public/CNAME new file mode 100644 index 0000000..30c2d4d --- /dev/null +++ b/web/public/CNAME @@ -0,0 +1 @@ +preview.pro.ant.design \ No newline at end of file diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e2e93252988b5bed120ddeb168eb577bf0caa881 GIT binary patch literal 4286 zcmeHLO(?8k6#ga#Gs=ufvobTd=q@y~nOLdYlqL%nQY4WqC`)z}3y~cqOAAF4VkQd~ z^3$wrC}~D1l%!eMp!|gA&U5sN?~b{2>)yNg-s#Lc-+9k_p7Y<=XqqYcDJs(Vt%dBG z=F~Lpdy*pd!Y zozZT}udJ-#| zqiAVq0sT@kAMMobbULxOw+F6{j*egO8CQ?TgTuo^+}_@zrlv;l$*-)e#PRVl?(Xgo zi9`?zg>ZFsh0DuJ)Ya9!von7LPN@OfP9IfH_4tet&&I~a#O{TK1<3*LO-xJ( zZ*Fc5%v&m4#Z&mZySs8{XJ-fV^Yh@{p`ju8d_J)w6+U%`!(qAC+uJK~qg_o+O|QC` zTVg0Y{jSHqzP^^&G0t4^cwF+$`egj?@9$AtTMLuPB>7!lUKY8&zP?xd=H_PM(U;HS ztNi!(_oJes0<5X6tu4{BxVR{FO)_x3UauFkv$I%RTSHe@7dAIHF+4mhc?|>t(!XbCXVQZk z8yje7Xh2R*&WCwdw&?N4$H!4$Uyq)i9_;V$3;y)^-LKhmkv{Z#(CP^-7Wn# zI5-H>z`y`nTU%u=f7Tbh-mg5fPT0G8`Wx6^vD1(>-r0;AFlyjWYC!!D@UQlt)%}4^ literal 0 HcmV?d00001 diff --git a/web/public/icons/icon-128x128.png b/web/public/icons/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..48d0e2339a60a637b94319c65e8654289b4f4b6c GIT binary patch literal 1329 zcmV-11C0002qP)t-s01zMl z|Nj6D8~_Fu01Fxg6ePmN$p8o!Wo~x>2^n*Hg!uURG(b#MUupdO{0J2!4IC;FATIs> z{@dN(0umtr1r+b_@!;a*CNn?_87S=S?pR`MN>yMYFg?=M*UQe)wYk2qw7Hs~re<$= zUubdX=<2(_!=9w6l9``_ijrw^dn`Fc6(TV7_4V=c^R2SBrmV4+ouiMHoQsl~euj;D zf{I&ZZXPQ+87DQ^+ugv!$3;(CLQPmWL{HY)+rGlaqNuHRe}`^&fKXdyK1x+FK1#;P z&4`be_4fDTmuWjt2mKnbI+d3dfa z_PK3Vs;zn;Ot~u#==o*X;a^nmOa{(_o(tmupuw*`!> z#lUp|BbBD0;J$!S0To9DO4Z9XkCjq#?=s{WNE{XLCFmLibLLfxLm3nt7Wj-&k{L;X zo_Y$L6(Ax^@lhJo2uNHK09we3_>jt`DV`HX8L^}WDJO|ULbS4T1b_}iycq`oGqNH4 zQJi@^@H`%ly#;k8%FIH(J#9^VH>(ITJX3)9M7)?mVD;pNM){2bV!rABC{yFeZXJ$q ztx`Yw*~Rj#FGR_Att-g zD?cOlXM^rD8S|s+a(LB7MePP8cKc zywqQW@o0gm8Qu`-)UW-9#K*uQ5WQT5&CGnc%phPyF4p1{wj=@#5xikX3uI#(QrZ2A z66>Nxz+a;r*Km#iEGXxR4BFJu%9#^UG{?XqFlA~7sH!J&2X_C6*P7#50#^tYfm`)p zW2Pupno-5;QoKQ)pCj;SS_MKc4Ai#W7h-YyLi$8b7l@8b3KjwX8wCk}6<4mh7hj6_ z@bnBUa)|InAVBSV)+C{3Cf$?e`b2@nQysp0$(swOoN^Eg(SO9p-Q1JJAAwzQCMaKm&bGV`U^9&pp0Gf^+nkRf5 zTo3@>xi{%p{gIpTF5iJBic@aIws^IC=-v{M~W@Y%aj8G0o!r2b|HdROb8D$LP+n;FWCW`~3a=G(k;6PFes57kh(>00a~S6Cwx} zCE()Z^7Hf|E zp{K0H$jr97ziM-QXK;BCA1!ozgl>0$_4f8XNmSCn^FkCmKwfQVshbU{p46e2L| z?Ct65?BnI;*4f+4&(glZ#fXoVgo~3^UusNOVjC$ov9`LWtg%K>TsK2c$;{8e#mKwA z!>qBksjjnFVr-b6q?4MUQCw!>t&JT300tCEL_t(|+U?tESK2@nfZ>~&tE5s!683!u zK~Xjr+;FLD`~QDWsaSMof{9Cdrsv$}Q$GacB{NB8tUw4Mgb+dqA%qY@2qA(L2bqdsgXsNTkuvsd$l2J=n5?~ND zV51VpX8=z3{QjGMB}&;~4lO{w(%Zazp$0@daWIEhfP~dcYQd#z&@Au`02`W0K*FSZ zfqww-)R9s!Ht2yOKmf3z60nW%2IP#}oH2Xl0XAm~+{S+rP&t$X9^nbbm-x&1;@!yj zY%9B7sWA>ZWO1jKtCah(e))TD=Eenv9so{D4rn_thqYd2VK|ELzaOufx!t$^NZw>p z+Iyj>U-gQu^kN;GPyD#d{Hh5IJ3uJYc+Z77Ufo$Ggo+WA3Y*@1Ky13)LZP31QSeOBPQ+XWH1FRE< znBBKzO$T4zWLDa@R>>EuJj^qYQEmht0qwfj(!SqQ@iKSEQ|m2>2OGl+_^GUFJOcJ= zXWHkFZpC2%v9sp4{dDSJXp{It8QP8>kQnV+s>T(A1*ey{eEoDWoB-?=l!qbE1M&?^ zlg>TQVZNQW0vG^>5+Gs?WoT;w-lorADbq%mw`k7}=L`%i*#i$2a3L12&mCI9?2mZ} z0W6}7?qr1p3pjZ?4xKwIs2_Fw@&LQuwzS(*uz)OwIWgMMHdb!T{tPE4SL`S{H8xBn!v4)5iQu&kynG zu&g>v%A749U2|<^9&fVG1>_^~CFZ7LP5t_J?7+%^=uNif%+;?)oeFwB$`&F5UWnO^ z@}M}gKew%NvbE6ZWlDEPmWMOGGN93O87@!W;veOx{0)tnve;1G63u~Z#6xV7>iY035`Xj%Y> z^Q5IJ+!Y6p_dlYRYQ(^}0Dwm;s0$Yi48Tdj3g$4vO$-3uKeU2KKGd0O0O0VXbm7s4;+l9&n%p_`cvI z?LB&meH%ahXIS9}0Jn2n$9YS(u{W#$gO9RPz0KE#GlYO}vMs$dg#n^%CpUU*6%xDq zjs0(lqaT-@s*4=qi*zHU+H4m-VgiXywKHFAtzxV0RWJzsovKI0FJxD0TBWR9R(cUae(Kj zsHF%%MItH28Xp(4*{bSm0T9Fm0Q3xia~uV&0pKeFz=jn7(rEx(_sVL}lf@aG=xQ21 z0F-Q4EcX2Td}n9x&+_W8>Dj@bW1Zc-_04UCC6!U}srDWLO4_FE!gBaD92g8{VR7ke zeM?AW92o=O(D2yXclnR)y=d4(E-o&bT05P*g06FjUtV6efBWI#2OS@uWaJd^OFd|4 zX;(M2f>E+f{F>2!?AX@%T}(mq>gp=@<7Y-5siUJ~-_R&H)yX)dzBXt+ zw3(irC-{#uAUx*V_rCAF0|y6(^qk`23R+NfVtI9aeo@&CE(t4VpY6XpJmUB8uHDiy zwv9=Ab9i`ka&k&{ODsM)GxBB9kG{dJ?Y|`SJP2xb|L_=>Cn3tZ=GbsG#NjrD{p8lZ+I*;#ZnN-&`)3V#iUD zMPI6J3dwTaR#efm@D7ZSx~Kc$)0fQLLZ9GgMwTwT5=zbJZ|@6AUcO3G4f|Pz+YOkF zhRS^y7A`o43kXqaS`P?+li?F^ab$DRnFBz%pmtx;FkotX)-fdI0SkIBo$3`kjGW}D zZoDeqLtS3Q2M@@N^6~Cb6R{D+s3aFqxA6=o*eHod92P`F`24wEin51>RC@xG+Cw7; zO)`T9mP{{KyOZ{F0<#_c5Bhd4GTu4*uXH<{{3_)6x4r)lr){`JohSKG%?$A}zhMc= zW|@O)q8WsJQ6EdzmKROG;U@_#(HhDuk8bo;Ho`*6l-2u9HLE*dOv$E=6gvnZLNCqP zprds6w}?9hZO+b`b-ggB&>$s;nRQSt|AE@)FA7Ls_I33&;P}~m=VyWbcR}f(OIN21 zGvpB_DYkJ<3MQsNhOEYodj;5({(pE0$4P!F$)xqVIwo83~69g=48RK+49na7Q(C zC7>Jw*DbzEje?QUe|LGt3nX|7Uz)%oP2zR&b4Op@R)(SMB<|5c3@$a63w&>G*}*

*C7T$?5!b= z&nPVm(<;~Sw_*kJlio|*qhI8#F?9y#@jvQ4Y?j2~r_a^)YoQ-co4WFOwF)i5mFnrV zp3rz^`K|nrI`guQ$X^%JuCI5qSS(}Tc|%+~o}P6dC={o1Q&<)3lnFnXE=l2Po$K4W zYZ0|2<3Cp|&)NE>;p=Muwg#BLXuboTJ(mwtjLbtdOE_A4n^}l29p9@>4vpS+t*^qG zb2Uo-W$@vKeR}iztf6tJTUh8=^kh_EBe^3f?1b(p_oBq&dpqW$pAyBB**bIt4BhwK zX02JE@mc}FExq0q+5_8B#EP&@;6-lx4t~)@$HBEBXnIqX0E$mhb$(} zt|vYUzekLn0SQ;Cd)J1 zXF3gv$u52+$>oQVvh7DxPpKeWD4yJgdWQQ`D#($Z=j%Df!iAxvR)-y7Ie5nI39+zG z4a3_4mE;SBh@MK8p%(n|yGj>Ao>j51ETLI#vuUt#g|%FDZG}&kbEb(|*9!RyL&U?| zvNnv+oEL_*R{Mw_;x+O`vNWUXL6>@M)sQr_lmlng7?sCaDH0YaGf%+R1ZM}9#$j$3 zi}N01^7uoRgvG74Fxi{415}={tg=gDHuqwR$1?9qlK<#~ zLG@an>_SlEY6+k8PD+S)C&NU7!~`$+Sin{JS2aLn_Jm#q|8H%OnLqt&n-f~C7KW9K z>QxTE1D!cV+(E>-8N#r>Aw98T*8=odDM_y2&?=ag7-=Rj&p%xvU zq=o{%T2@T(b@VqinV#`~IN4t3^z$HE=3~ZiDQ=xP6Gt)g;q1Wv2N&CBx=Ndw5 z6U*fS@;@VBF^MGsyag~+Eq&2UZ@5SG$!SM2ckBL!E1lBg>*t^{Lqn=cK^-Rhc#S(% zuAqbI(^+PqN#)t&5xr~v&*Y$qZ$3Yz)nRe`ZZcJUU8hd=)9$7 z6n-k7p||Esq}igp(IoxtD&_5A!OwcTvM3p-HHJGcVh|>N13O;!W%RXd{Y6@BEp1Jz zHCH!~H~lR%<3E0b>}>_dD6Ef8r_)LSp}O5L`DerkKk|Jc@n6*BmU&r@TKhG$-x=>`wv*Vj%M=6<7!P7~3+)?^D>AA~&^F+{mzBAMLh zuTiItS}IqcPpj-t;W15ahnBV9rHnrShN-@GCY+EyXV6Gass&4cM~^;F?z{3+SXKe7 zX8rBU7Fc!Kr5M!K@lDd)Bl)VMR)yb6;AHxpH?Q1Bx14XQy~`&ggyC$4u>az;q|C7G ziTI-A3y9BZbxP7rW4_G!ju{^(NOZstl}tvVR+j8_B`~)2cWn2~;amI9u@_9oSC+Xc z_+DcYbc7S~MIl7Fjk6qEWXDo6>n5U0O4P_k15Z^c%P@0?*Oaj8%BIU*uy|Vgi@eqH zPKZqrW$wXWQ9=QoQrY4VBCFzACRJhEv+U(FOmx1_uwVirtukLVbyF&%?xKU6D>Yot zj6ZW-1$@|ASwJlAu1;!K9!5uPdov-?7RK^;L%bs%6z9abcvP!i20^{1|g}`{&O=}R+#D;J+z#Fj(x6k%xY0!5_DrzSsTCn;pu^6 zIZV~_c4b?yxFutJZTc`BT4+(N21dx%Bxx5_=*+^Gl`PCmvS7QnNZUr>RqS>smtAo7 z%1fV4h6YI+Y2X$?ZuQmeFj!XWfPJxAc1<0;;3C|?V?=BITB{AnSkYW;+Vbas9ODwo zC?uf|l|KjLdt@iP*&!CGbh&OT1l3&s3RD|7knGOc{T%o)WLqmqSnTE5-M^OKN+8ct1C(XwQJJg%OozV)cJ^w# zVxbg~z7OMwn+eEPaBRR0KIDX14Kb^G-Strc8r6QmoINnF;F1;|U5n3%rdT9(U0dcP zI*}ZU>2wFp1u1KmK2cwSwk|=oZ8?l*@yIPBD3BPN(BINzFh#VAKBpVxZ71*JgwpWXLZfS%KkGa_tQn0bX5)h8 z6Q7LGdb=)NQ}a}>SD{8~!ga|bE*aS_Nxg_+ojhQ1{b@sVzaX)Z{rV-1%Qa*ioSy65 zb4ke$#&Mf>xuBg{&aCGNbhbOGbN4XT4?qQpRW^!2z;9eCH6fH7)+qpWe;n~*tCIq= z&w&jc@_tBWRiS3IyovPwt~fl#1Z-%o%T_B%)sO*FrOIkz-1r$#mr+&N2J06a?u1<~QB}EoR?wnU)c32Ew0(EP^w=K@< zM;7MeoWTwa-$09MwD#{-p5K@kGTM6q@*9`~NBz#snAQt$W%fZr`qk|A%IEM4|B(?x zOpx+g1-*Q?a%JHpKf|Cs>MCBj^^EmKDb?*U1(_uF({whek67oi^wL)^XH3=`&u$U^ z*K&AEIc$ISs)O2r7emK9XW_i^6Jxw-Ug6tIn{3gqAYG+j_U1<)>HovW7l7Z>STIh4 z7OH$pfQM^<6ZPN`%FY^PFKzq89tYsIi0BGroup 28 Copy 5Created with Sketch. \ No newline at end of file diff --git a/web/public/pro_icon.svg b/web/public/pro_icon.svg new file mode 100644 index 0000000..e075b78 --- /dev/null +++ b/web/public/pro_icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/web/public/scripts/loading.js b/web/public/scripts/loading.js new file mode 100644 index 0000000..a4fdb3d --- /dev/null +++ b/web/public/scripts/loading.js @@ -0,0 +1,202 @@ +/** + * loading 占位 + * 解决首次加载时白屏的问题 + */ +(function () { + const _root = document.querySelector('#root'); + if (_root && _root.innerHTML === '') { + _root.innerHTML = ` + + +

+
+
+ + + + + + +
+
+
+ 正在加载资源 +
+
+ 初次加载资源可能需要较多时间 请耐心等待 +
+
+ `; + } +})(); diff --git a/web/src/access.ts b/web/src/access.ts new file mode 100644 index 0000000..be9717e --- /dev/null +++ b/web/src/access.ts @@ -0,0 +1,13 @@ +import type { Admin } from "@/services"; + +/** + * @see https://umijs.org/docs/max/access#access + * */ +export default function access( + initialState: { currentUser?: Admin } | undefined, +) { + const { currentUser } = initialState ?? {}; + return { + canAdmin: currentUser && currentUser.access === "admin", + }; +} diff --git a/web/src/app.tsx b/web/src/app.tsx new file mode 100644 index 0000000..808ce72 --- /dev/null +++ b/web/src/app.tsx @@ -0,0 +1,143 @@ +import { + AvatarDropdown, + AvatarName, + Footer, + Question, + SelectLang, +} from "@/components"; +import { type Admin, services } from "@/services"; +import type { Settings as LayoutSettings } from "@ant-design/pro-components"; +import { SettingDrawer } from "@ant-design/pro-components"; +import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max"; +import { history } from "@umijs/max"; +import defaultSettings from "../config/defaultSettings"; +import { errorConfig } from "./requestErrorConfig"; + +const isDev = process.env.NODE_ENV === "development"; +const isDevOrTest = isDev || process.env.CI; +const loginPath = "/user/login"; + +/** + * @see https://umijs.org/docs/api/runtime-config#getinitialstate + * */ +export async function getInitialState(): Promise<{ + settings?: Partial; + currentUser?: Admin; + loading?: boolean; + fetchUserInfo?: () => Promise; +}> { + const fetchUserInfo = async () => { + try { + return await services.admin.Current({}); + } catch { + // Redirecting on 401 is handled globally in requestErrorConfig. + return undefined; + } + }; + // 如果不是登录页面,执行 + const { location } = history; + if ( + ![loginPath, "/user/register", "/user/register-result"].includes( + location.pathname + ) + ) { + const currentUser = await fetchUserInfo(); + return { + fetchUserInfo, + currentUser, + settings: defaultSettings as Partial, + }; + } + return { + fetchUserInfo, + settings: defaultSettings as Partial, + }; +} + +// ProLayout 支持的api https://procomponents.ant.design/components/layout +export const layout: RunTimeLayoutConfig = ({ + initialState, + setInitialState, +}) => { + return { + actionsRender: () => [ + , + , + ], + avatarProps: { + src: initialState?.currentUser?.avatar, + title: , + render: (_, avatarChildren) => ( + {avatarChildren} + ), + }, + waterMarkProps: { + content: initialState?.currentUser?.name, + }, + footerRender: () =>