commit be5362913b70abc2e99ea13b16f6e305cedddd8b Author: yvan Date: Thu Aug 13 18:22:43 2026 +0800 feat: initial commit - kratos admin service 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 0000000..e2e9325 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/public/icons/icon-128x128.png b/web/public/icons/icon-128x128.png new file mode 100644 index 0000000..48d0e23 Binary files /dev/null and b/web/public/icons/icon-128x128.png differ diff --git a/web/public/icons/icon-192x192.png b/web/public/icons/icon-192x192.png new file mode 100644 index 0000000..938e9b5 Binary files /dev/null and b/web/public/icons/icon-192x192.png differ diff --git a/web/public/icons/icon-512x512.png b/web/public/icons/icon-512x512.png new file mode 100644 index 0000000..21fc108 Binary files /dev/null and b/web/public/icons/icon-512x512.png differ diff --git a/web/public/logo.svg b/web/public/logo.svg new file mode 100644 index 0000000..239bf69 --- /dev/null +++ b/web/public/logo.svg @@ -0,0 +1 @@ +Group 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: () =>