From acba9624d70e9b42b838274006318239c13fe627 Mon Sep 17 00:00:00 2001 From: yvan <8574526@qq.com> Date: Fri, 14 Aug 2026 00:28:37 +0800 Subject: [PATCH] feat: complete Kra admin backend migration --- README.md | 102 ++- api/kratos/admin/v1/admin.pb.go | 710 ----------------- api/kratos/admin/v1/admin.proto | 161 ---- api/kratos/admin/v1/admin_grpc.pb.go | 408 ---------- api/kratos/admin/v1/admin_http.pb.go | 390 ---------- api/kratos/admin/v1/error_reason.pb.go | 153 ---- api/kratos/admin/v1/error_reason.proto | 27 - cmd/kratos-admin/main.go | 4 +- cmd/kratos-admin/wire_gen.go | 10 +- configs/config.yaml | 5 +- go.mod | 45 +- go.sum | 104 +-- internal/biz/access.go | 9 + internal/biz/admin.go | 136 ---- internal/biz/admin_test.go | 166 ---- internal/biz/biz.go | 2 +- internal/biz/errors.go | 6 +- internal/biz/media.go | 20 + internal/biz/settings.go | 1 + internal/biz/system.go | 13 +- internal/biz/task.go | 3 +- internal/conf/conf.pb.go | 238 +++--- internal/conf/conf.proto | 11 +- internal/data/access.go | 219 +++++- internal/data/admin.go | 85 -- internal/data/aliyun_storage.go | 105 +++ internal/data/announcement.go | 4 +- internal/data/aws_storage.go | 152 ++++ internal/data/cache.go | 28 +- internal/data/config_store.go | 85 +- internal/data/data.go | 63 +- internal/data/database.go | 17 +- internal/data/email.go | 27 +- internal/data/ent/admin.go | 172 ----- internal/data/ent/admin/admin.go | 116 --- internal/data/ent/admin/where.go | 510 ------------ internal/data/ent/admin_create.go | 859 --------------------- internal/data/ent/admin_delete.go | 88 --- internal/data/ent/admin_query.go | 527 ------------- internal/data/ent/admin_update.go | 382 --------- internal/data/ent/client.go | 366 --------- internal/data/ent/ent.go | 608 --------------- internal/data/ent/enttest/enttest.go | 84 -- internal/data/ent/generate.go | 3 - internal/data/ent/hook/hook.go | 199 ----- internal/data/ent/migrate/migrate.go | 64 -- internal/data/ent/migrate/schema.go | 35 - internal/data/ent/mutation.go | 684 ---------------- internal/data/ent/predicate/predicate.go | 10 - internal/data/ent/runtime.go | 48 -- internal/data/ent/runtime/runtime.go | 10 - internal/data/ent/schema/admin.go | 27 - internal/data/ent/tx.go | 236 ------ internal/data/file_storage.go | 64 +- internal/data/huawei_storage.go | 104 +++ internal/data/json_type.go | 48 ++ internal/data/media.go | 13 + internal/data/migrations.go | 16 + internal/data/s3_storage.go | 3 - internal/data/settings.go | 35 +- internal/data/system.go | 136 +++- internal/data/task.go | 29 +- internal/data/tencent_storage.go | 117 +++ internal/server/audit_routes.go | 38 +- internal/server/gin.go | 93 ++- internal/server/grpc.go | 33 - internal/server/http.go | 37 - internal/server/menu_routes.go | 22 +- internal/server/server.go | 2 +- internal/server/settings_routes.go | 16 +- internal/server/system_config_routes.go | 68 +- internal/server/task_routes.go | 12 +- internal/server/task_scheduler.go | 29 + internal/service/admin.go | 232 ------ internal/service/service.go | 2 +- internal/service/settings.go | 16 +- internal/service/system.go | 40 +- internal/service/task.go | 49 +- openapi.yaml | 249 ------ pkg/adminauth/token.go | 4 +- pkg/auth/auth.go | 32 - pkg/auth/config.go | 22 - pkg/auth/middleware.go | 100 --- pkg/auth/middleware_test.go | 125 --- pkg/auth/token.go | 48 -- web/src/assets/cover-redesign.svg | 4 +- web/src/view/layout/search/search.vue | 5 - web/src/view/systemTools/system/system.vue | 8 +- 88 files changed, 1765 insertions(+), 8623 deletions(-) delete mode 100644 api/kratos/admin/v1/admin.pb.go delete mode 100644 api/kratos/admin/v1/admin.proto delete mode 100644 api/kratos/admin/v1/admin_grpc.pb.go delete mode 100644 api/kratos/admin/v1/admin_http.pb.go delete mode 100644 api/kratos/admin/v1/error_reason.pb.go delete mode 100644 api/kratos/admin/v1/error_reason.proto delete mode 100644 internal/biz/admin.go delete mode 100644 internal/biz/admin_test.go delete mode 100644 internal/data/admin.go create mode 100644 internal/data/aliyun_storage.go create mode 100644 internal/data/aws_storage.go delete mode 100644 internal/data/ent/admin.go delete mode 100644 internal/data/ent/admin/admin.go delete mode 100644 internal/data/ent/admin/where.go delete mode 100644 internal/data/ent/admin_create.go delete mode 100644 internal/data/ent/admin_delete.go delete mode 100644 internal/data/ent/admin_query.go delete mode 100644 internal/data/ent/admin_update.go delete mode 100644 internal/data/ent/client.go delete mode 100644 internal/data/ent/ent.go delete mode 100644 internal/data/ent/enttest/enttest.go delete mode 100644 internal/data/ent/generate.go delete mode 100644 internal/data/ent/hook/hook.go delete mode 100644 internal/data/ent/migrate/migrate.go delete mode 100644 internal/data/ent/migrate/schema.go delete mode 100644 internal/data/ent/mutation.go delete mode 100644 internal/data/ent/predicate/predicate.go delete mode 100644 internal/data/ent/runtime.go delete mode 100644 internal/data/ent/runtime/runtime.go delete mode 100644 internal/data/ent/schema/admin.go delete mode 100644 internal/data/ent/tx.go create mode 100644 internal/data/huawei_storage.go create mode 100644 internal/data/json_type.go create mode 100644 internal/data/migrations.go create mode 100644 internal/data/tencent_storage.go delete mode 100644 internal/server/grpc.go delete mode 100644 internal/server/http.go delete mode 100644 internal/service/admin.go delete mode 100644 openapi.yaml delete mode 100644 pkg/auth/auth.go delete mode 100644 pkg/auth/config.go delete mode 100644 pkg/auth/middleware.go delete mode 100644 pkg/auth/middleware_test.go delete mode 100644 pkg/auth/token.go diff --git a/README.md b/README.md index 65b2e0d..45bd701 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,57 @@ -# Kratos Admin Template +# Kra -## Best Practice -Google AIP(https://google.aip.dev/general): -1. Resource-oriented design -2. Filtering -3. Pagination -4. Field masks -5. Field behavior +Kra 是基于 Kratos 生命周期与 Wire 依赖注入、使用 Gin 提供管理端 HTTP API 的前后端管理系统。 + +## 目录 + +```text +cmd/kratos-admin/ 服务入口与 Wire +configs/ 运行配置 +internal/biz/ 领域对象、用例和仓储接口 +internal/data/ 数据库、缓存、对象存储及仓储实现 +internal/server/ Gin 服务、路由和定时任务调度 +internal/service/ HTTP 输入输出与领域对象转换 +web/ Vue 管理端 +``` + +## 启动后端 + +先修改 `configs/config.yaml`。首次启动时,如果目标数据库不可用,服务会保留初始化接口;在管理端初始化页面完成建库、建表和种子数据写入后即可登录。 + +```shell +go run ./cmd/kratos-admin -conf ./configs +``` + +默认 HTTP 地址为 `http://127.0.0.1:8000`。生产环境必须替换 JWT 签名密钥和数据库凭据。 + +## 启动前端 + +```shell +cd web +pnpm install +pnpm dev +``` + +构建产物和依赖目录 `web/dist/`、`web/node_modules/` 已加入 Git 忽略规则。 + +## 生成代码 + +修改 `internal/conf/conf.proto` 后执行: -## 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 +修改 Wire Provider 或构造函数后执行: + ```shell -# Enter web directory, install dependencies and start development server -cd web -npm install -npm run dev +go generate ./cmd/kratos-admin ``` -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); - } -}; +```shell +go test ./... +go vet ./... +cd web && pnpm lint && pnpm build ``` diff --git a/api/kratos/admin/v1/admin.pb.go b/api/kratos/admin/v1/admin.pb.go deleted file mode 100644 index e430c12..0000000 --- a/api/kratos/admin/v1/admin.pb.go +++ /dev/null @@ -1,710 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11 -// protoc v7.35.1 -// 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\"r\n" + - "\fLoginRequest\x12 \n" + - "\bpassword\x18\x01 \x01(\tB\x04\xe2A\x01\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\x14\n" + - "\x02id\x18\x01 \x01(\x03B\x04\xe2A\x01\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\"H\n" + - "\x12CreateAdminRequest\x122\n" + - "\x05admin\x18\x01 \x01(\v2\x16.kratos.admin.v1.AdminB\x04\xe2A\x01\x02R\x05admin\"\x8b\x01\n" + - "\x12UpdateAdminRequest\x122\n" + - "\x05admin\x18\x01 \x01(\v2\x16.kratos.admin.v1.AdminB\x04\xe2A\x01\x02R\x05admin\x12A\n" + - "\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskB\x04\xe2A\x01\x02R\n" + - "updateMask\"*\n" + - "\x12DeleteAdminRequest\x12\x14\n" + - "\x02id\x18\x01 \x01(\x03B\x04\xe2A\x01\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}B3\n" + - "\x13api.kratos.admin.v1P\x01Z\x1akra/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 deleted file mode 100644 index 3685257..0000000 --- a/api/kratos/admin/v1/admin.proto +++ /dev/null @@ -1,161 +0,0 @@ -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 deleted file mode 100644 index 4b46c28..0000000 --- a/api/kratos/admin/v1/admin_grpc.pb.go +++ /dev/null @@ -1,408 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.6.2 -// - protoc v7.35.1 -// 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 deleted file mode 100644 index e3a8edc..0000000 --- a/api/kratos/admin/v1/admin_http.pb.go +++ /dev/null @@ -1,390 +0,0 @@ -// Code generated by protoc-gen-go-http. DO NOT EDIT. -// versions: -// - protoc-gen-go-http v3.0.0 -// - protoc v7.35.1 -// 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 deleted file mode 100644 index e81186f..0000000 --- a/api/kratos/admin/v1/error_reason.pb.go +++ /dev/null @@ -1,153 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11 -// protoc v7.35.1 -// 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\x05B3\n" + - "\x13api.kratos.admin.v1P\x01Z\x1akra/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 deleted file mode 100644 index 9bed116..0000000 --- a/api/kratos/admin/v1/error_reason.proto +++ /dev/null @@ -1,27 +0,0 @@ -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/cmd/kratos-admin/main.go b/cmd/kratos-admin/main.go index 4032052..31674a9 100644 --- a/cmd/kratos-admin/main.go +++ b/cmd/kratos-admin/main.go @@ -14,7 +14,6 @@ import ( "github.com/go-kratos/kratos/v3/config" "github.com/go-kratos/kratos/v3/config/file" "github.com/go-kratos/kratos/v3/log" - "github.com/go-kratos/kratos/v3/transport/grpc" _ "go.uber.org/automaxprocs" ) @@ -35,7 +34,7 @@ func init() { flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml") } -func newApp(logger *slog.Logger, gs *grpc.Server, hs *server.GinServer, scheduler *server.TaskScheduler) *kratos.App { +func newApp(logger *slog.Logger, hs *server.GinServer, scheduler *server.TaskScheduler) *kratos.App { return kratos.New( kratos.ID(id), kratos.Name(Name), @@ -43,7 +42,6 @@ func newApp(logger *slog.Logger, gs *grpc.Server, hs *server.GinServer, schedule kratos.Metadata(map[string]string{}), kratos.Logger(logger), kratos.Server( - gs, hs, scheduler, ), diff --git a/cmd/kratos-admin/wire_gen.go b/cmd/kratos-admin/wire_gen.go index 5c1a71a..320acf6 100644 --- a/cmd/kratos-admin/wire_gen.go +++ b/cmd/kratos-admin/wire_gen.go @@ -28,13 +28,9 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad if err != nil { return nil, nil, err } - adminRepo := data.NewAdminRepo(dataData) - adminUsecase := biz.NewAdminUsecase(adminRepo) - adminService := service.NewAdminService(adminUsecase) - grpcServer := server.NewGRPCServer(confServer, adminService) systemRepo := data.NewSystemRepo(dataData) cache := data.NewCache(dataData) - fileStorage, err := data.NewFileStorage(adminBackend) + fileStorage, err := data.NewFileStorage(dataData) if err != nil { cleanup() return nil, nil, err @@ -58,9 +54,9 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad auditService := service.NewAuditService(auditUsecase) taskRepo := data.NewTaskRepo(dataData) taskUsecase := biz.NewTaskUsecase(taskRepo) - taskService := service.NewTaskService(taskUsecase) mediaRepo := data.NewMediaRepo(dataData) mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage) + taskService := service.NewTaskService(taskUsecase, mediaUsecase, adminBackend) mediaService := service.NewMediaService(mediaUsecase) announcementRepo := data.NewAnnouncementRepo(dataData) announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo) @@ -70,7 +66,7 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad emailService := service.NewEmailService(emailUsecase) taskScheduler := server.NewTaskScheduler(taskService, logger) ginServer := server.NewGinServer(confServer, adminBackend, systemService, accessService, settingsService, versionService, exportService, auditService, taskService, mediaService, announcementService, emailService, taskScheduler, logger) - app := newApp(logger, grpcServer, ginServer, taskScheduler) + app := newApp(logger, ginServer, taskScheduler) return app, func() { cleanup() }, nil diff --git a/configs/config.yaml b/configs/config.yaml index 23e9322..d483277 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -2,9 +2,6 @@ server: http: addr: 0.0.0.0:8000 timeout: 1s - grpc: - addr: 0.0.0.0:9000 - timeout: 1s data: database: driver: mysql @@ -35,6 +32,8 @@ admin: local: store_path: uploads/file path_prefix: uploads/file + media: + session_ttl: 24 storage: # local, qiniu, aliyun-oss, huawei-obs, tencent-cos, aws-s3, # cloudflare-r2 or minio diff --git a/go.mod b/go.mod index cc5e208..b71bd53 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,12 @@ module kra go 1.25.7 require ( - entgo.io/ent v0.14.6 + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/aws/aws-sdk-go-v2 v1.41.2 + github.com/aws/aws-sdk-go-v2/config v1.32.10 + github.com/aws/aws-sdk-go-v2/credentials v1.19.10 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1 github.com/casbin/casbin/v3 v3.10.0 github.com/dzwvip/gorm-oracle v0.1.2 github.com/gin-gonic/gin v1.10.0 @@ -13,18 +18,18 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/google/wire v0.7.0 + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible github.com/minio/minio-go/v7 v7.0.92 github.com/mojocn/base64Captcha v1.3.8 github.com/qiniu/go-sdk/v7 v7.25.2 github.com/redis/go-redis/v9 v9.7.0 github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v4 v4.25.7 + github.com/tencentyun/cos-go-sdk-v5 v0.7.60 github.com/xuri/excelize/v2 v2.9.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 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.6.0 @@ -36,18 +41,30 @@ require ( 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/BurntSushi/toml v1.3.2 // indirect - github.com/agext/levenshtein v1.2.3 // indirect github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect - github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect + github.com/aws/smithy-go v1.24.1 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/casbin/govaluate v1.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clbanning/mxj v1.8.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -63,7 +80,6 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/inflect v0.21.6 // indirect github.com/go-playground/form/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -74,10 +90,9 @@ require ( github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-querystring v1.0.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/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect @@ -93,10 +108,11 @@ require ( github.com/microsoft/go-mssqldb v1.8.2 // indirect github.com/minio/crc64nvme v1.0.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mozillazg/go-httpheader v0.2.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect @@ -114,21 +130,20 @@ require ( github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect github.com/yusufpapurcu/wmi v1.2.4 // 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/arch v0.8.0 // indirect golang.org/x/image v0.23.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 + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 // indirect + google.golang.org/grpc v1.81.1 // indirect modernc.org/fileutil v1.0.0 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect diff --git a/go.sum b/go.sum index e979a82..8224a73 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,3 @@ -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/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= @@ -26,16 +20,51 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -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/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= -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/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls= +github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c= +github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI= +github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw= +github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3 h1:+mQ8NQBh7B7c2FBtppRnwkrmuwFON1XQQ+5yblomZKk= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3/go.mod h1:u67RKh3BRmS4FYLH+rN3N4T5fqpd9m2ttAwBJYEdosU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 h1:IJRzQTvdpjHRPItx9gzNcz7Y1F+xqAR+xiy9rr5ZYl8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1 h1:giB30dEeoar5bgDnkE0q+z7cFjcHaCjulpmPVmuKR84= +github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1/go.mod h1:071TH4M3botFLWDbzQLfBR7tXYi7Fs2RsXSiH7nlUlY= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs= +github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0= +github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -52,12 +81,12 @@ github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9my github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 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/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -75,11 +104,6 @@ github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0o github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -107,8 +131,6 @@ 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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -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.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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= @@ -127,8 +149,6 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= @@ -150,9 +170,12 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= @@ -166,8 +189,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -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/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ4fSNB6mMRuYNvFWou7BZs6SZB925hPrnk= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= @@ -217,8 +240,6 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -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/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I= github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo= github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY= @@ -227,8 +248,9 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw= github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0= -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/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -240,6 +262,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd github.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg= github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= @@ -248,8 +272,6 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= @@ -299,6 +321,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= +github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o= +github.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0= github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y= github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= @@ -320,12 +346,6 @@ github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -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= @@ -368,8 +388,6 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -457,14 +475,14 @@ golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= -golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= diff --git a/internal/biz/access.go b/internal/biz/access.go index 9f90c2c..4148aa5 100644 --- a/internal/biz/access.go +++ b/internal/biz/access.go @@ -20,6 +20,15 @@ type MenuButton struct { Name string Description string MenuID uint + AuthorityID uint +} + +type MenuParameter struct { + ID uint + MenuID uint + Type string + Key string + Value string } type Department struct { diff --git a/internal/biz/admin.go b/internal/biz/admin.go deleted file mode 100644 index 435a2dc..0000000 --- a/internal/biz/admin.go +++ /dev/null @@ -1,136 +0,0 @@ -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 deleted file mode 100644 index 7d87f7f..0000000 --- a/internal/biz/admin_test.go +++ /dev/null @@ -1,166 +0,0 @@ -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 index 5d2ab99..3de12e2 100644 --- a/internal/biz/biz.go +++ b/internal/biz/biz.go @@ -3,4 +3,4 @@ package biz import "github.com/google/wire" // ProviderSet is biz providers. -var ProviderSet = wire.NewSet(NewAdminUsecase, NewSystemUsecase, NewAccessUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) +var ProviderSet = wire.NewSet(NewSystemUsecase, NewAccessUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) diff --git a/internal/biz/errors.go b/internal/biz/errors.go index e87343e..6922727 100644 --- a/internal/biz/errors.go +++ b/internal/biz/errors.go @@ -1,14 +1,12 @@ 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") + ErrUserNotFound = errors.NotFound("USER_NOT_FOUND", "user 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") + ErrInvalidCredentials = errors.Unauthorized("INVALID_CREDENTIALS", "invalid credentials") ) diff --git a/internal/biz/media.go b/internal/biz/media.go index 792b1a9..12d6213 100644 --- a/internal/biz/media.go +++ b/internal/biz/media.go @@ -67,6 +67,8 @@ type MediaRepo interface { UpsertChunk(context.Context, uint, *UploadChunk) error ListChunks(context.Context, uint) ([]*UploadChunk, error) DeleteChunks(context.Context, uint) error + StaleUploadSessionIDs(context.Context, time.Time) ([]uint, error) + DeleteUploadData(context.Context, uint) error } type MediaUsecase struct { repo MediaRepo @@ -225,6 +227,24 @@ func (uc *MediaUsecase) CancelUpload(ctx context.Context, userID, uploadID uint) _ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID)) return uc.repo.DeleteUploadSession(ctx, uploadID) } +func (uc *MediaUsecase) CleanupStale(ctx context.Context, ttlHours int) error { + if ttlHours <= 0 { + ttlHours = 24 + } + ids, err := uc.repo.StaleUploadSessionIDs(ctx, time.Now().Add(-time.Duration(ttlHours)*time.Hour)) + if err != nil { + return err + } + for _, id := range ids { + if err = uc.repo.DeleteUploadData(ctx, id); err != nil { + return err + } + if err = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", id)); err != nil { + return err + } + } + return nil +} func (uc *MediaUsecase) ListStorage(ctx context.Context, prefix, cursor string, limit int) ([]*StoredFile, string, bool, error) { return uc.files.List(ctx, prefix, cursor, limit) } diff --git a/internal/biz/settings.go b/internal/biz/settings.go index 3725794..e7dcfc3 100644 --- a/internal/biz/settings.go +++ b/internal/biz/settings.go @@ -103,6 +103,7 @@ type SettingsRepo interface { CreateAPIToken(context.Context, *APIToken) error ListAPITokens(context.Context, int, int, uint, *bool) ([]*APIToken, int64, error) DisableAPIToken(context.Context, uint) (string, error) + BlacklistToken(context.Context, string) error IsTokenDisabled(context.Context, string) (bool, error) SecurityConfig(context.Context) (*SecurityConfig, error) SaveSecurityConfig(context.Context, *SecurityConfig) error diff --git a/internal/biz/system.go b/internal/biz/system.go index 9595a7e..6acb46a 100644 --- a/internal/biz/system.go +++ b/internal/biz/system.go @@ -36,7 +36,8 @@ type UserListFilter struct { } type DatabaseConfig struct { - Driver, Host, Port, User, Password, Name, Path, Config, AdminPassword string + Driver, Host, Port, User, Password, Name, Path, Config, Template, AdminPassword string + APIs []*API } type Authority struct { @@ -64,6 +65,7 @@ type Menu struct { TransitionType string Children []*Menu Buttons []*MenuButton + Parameters []*MenuParameter } type SystemRepo interface { @@ -82,6 +84,8 @@ type SystemRepo interface { SetUserSetting(context.Context, uint, map[string]any) error ListAuthorities(context.Context) ([]*Authority, error) PersistConfig(context.Context) error + PersistAdminConfig(context.Context, []byte) error + ReloadConfig(context.Context) error } type SystemUsecase struct { @@ -103,6 +107,9 @@ func (uc *SystemUsecase) CacheSet(ctx context.Context, key, value string, expira func (uc *SystemUsecase) CacheDelete(ctx context.Context, key string) error { return uc.cache.Delete(ctx, key) } +func (uc *SystemUsecase) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) { + return uc.cache.Increment(ctx, key, expiration) +} func (uc *SystemUsecase) IsInitialized(ctx context.Context) (bool, error) { return uc.repo.IsInitialized(ctx) @@ -114,6 +121,10 @@ func (uc *SystemUsecase) Initialize(ctx context.Context, config *DatabaseConfig) return uc.repo.Initialize(ctx, config) } func (uc *SystemUsecase) PersistConfig(ctx context.Context) error { return uc.repo.PersistConfig(ctx) } +func (uc *SystemUsecase) PersistAdminConfig(ctx context.Context, value []byte) error { + return uc.repo.PersistAdminConfig(ctx, value) +} +func (uc *SystemUsecase) ReloadConfig(ctx context.Context) error { return uc.repo.ReloadConfig(ctx) } func (uc *SystemUsecase) Login(ctx context.Context, username, password string) (*User, error) { u, err := uc.repo.FindUserByUsername(ctx, username) diff --git a/internal/biz/task.go b/internal/biz/task.go index d108c42..b4a4fe8 100644 --- a/internal/biz/task.go +++ b/internal/biz/task.go @@ -37,7 +37,8 @@ type TaskRepo interface { ToggleTask(context.Context, uint, bool) error RecordTaskLog(context.Context, *TimedTaskLog) error ListTaskLogs(context.Context, int, int, uint, string) ([]*TimedTaskLog, int64, error) - CleanupLogs(context.Context, time.Time) error + CleanupLogs(context.Context) error + TaskNameExists(context.Context, string, uint) (bool, error) } type TaskUsecase struct{ repo TaskRepo } diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go index 7602f6b..24633dd 100644 --- a/internal/conf/conf.pb.go +++ b/internal/conf/conf.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc (unknown) +// protoc v7.35.1 // source: conf/conf.proto package conf @@ -85,7 +85,6 @@ func (x *Bootstrap) GetAdmin() *AdminBackend { 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 } @@ -127,13 +126,6 @@ func (x *Server) GetHttp() *Server_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"` @@ -196,7 +188,8 @@ type AdminBackend struct { Email *AdminBackend_Email `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` Storage *AdminBackend_Storage `protobuf:"bytes,6,opt,name=storage,proto3" json:"storage,omitempty"` // ConfigPath is populated by the entrypoint and is not required in YAML. - ConfigPath string `protobuf:"bytes,7,opt,name=config_path,json=configPath,proto3" json:"config_path,omitempty"` + ConfigPath string `protobuf:"bytes,7,opt,name=config_path,json=configPath,proto3" json:"config_path,omitempty"` + Media *AdminBackend_Media `protobuf:"bytes,8,opt,name=media,proto3" json:"media,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -280,6 +273,13 @@ func (x *AdminBackend) GetConfigPath() string { return "" } +func (x *AdminBackend) GetMedia() *AdminBackend_Media { + if x != nil { + return x.Media + } + return nil +} + type Server_HTTP struct { state protoimpl.MessageState `protogen:"open.v1"` Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` @@ -340,66 +340,6 @@ func (x *Server_HTTP) GetTimeout() *durationpb.Duration { 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[5] - 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[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 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"` @@ -417,7 +357,7 @@ type Data_Database struct { func (x *Data_Database) Reset() { *x = Data_Database{} - mi := &file_conf_conf_proto_msgTypes[6] + mi := &file_conf_conf_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -429,7 +369,7 @@ func (x *Data_Database) String() string { func (*Data_Database) ProtoMessage() {} func (x *Data_Database) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[6] + mi := &file_conf_conf_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -520,7 +460,7 @@ type Data_Redis struct { func (x *Data_Redis) Reset() { *x = Data_Redis{} - mi := &file_conf_conf_proto_msgTypes[7] + mi := &file_conf_conf_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -532,7 +472,7 @@ func (x *Data_Redis) String() string { func (*Data_Redis) ProtoMessage() {} func (x *Data_Redis) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[7] + mi := &file_conf_conf_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -588,7 +528,7 @@ type AdminBackend_JWT struct { func (x *AdminBackend_JWT) Reset() { *x = AdminBackend_JWT{} - mi := &file_conf_conf_proto_msgTypes[8] + mi := &file_conf_conf_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -600,7 +540,7 @@ func (x *AdminBackend_JWT) String() string { func (*AdminBackend_JWT) ProtoMessage() {} func (x *AdminBackend_JWT) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[8] + mi := &file_conf_conf_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -656,7 +596,7 @@ type AdminBackend_Captcha struct { func (x *AdminBackend_Captcha) Reset() { *x = AdminBackend_Captcha{} - mi := &file_conf_conf_proto_msgTypes[9] + mi := &file_conf_conf_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -668,7 +608,7 @@ func (x *AdminBackend_Captcha) String() string { func (*AdminBackend_Captcha) ProtoMessage() {} func (x *AdminBackend_Captcha) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[9] + mi := &file_conf_conf_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -722,7 +662,7 @@ type AdminBackend_Local struct { func (x *AdminBackend_Local) Reset() { *x = AdminBackend_Local{} - mi := &file_conf_conf_proto_msgTypes[10] + mi := &file_conf_conf_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -734,7 +674,7 @@ func (x *AdminBackend_Local) String() string { func (*AdminBackend_Local) ProtoMessage() {} func (x *AdminBackend_Local) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[10] + mi := &file_conf_conf_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -780,7 +720,7 @@ type AdminBackend_Email struct { func (x *AdminBackend_Email) Reset() { *x = AdminBackend_Email{} - mi := &file_conf_conf_proto_msgTypes[11] + mi := &file_conf_conf_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -792,7 +732,7 @@ func (x *AdminBackend_Email) String() string { func (*AdminBackend_Email) ProtoMessage() {} func (x *AdminBackend_Email) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[11] + mi := &file_conf_conf_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -864,6 +804,50 @@ func (x *AdminBackend_Email) GetIsLoginAuth() bool { return false } +type AdminBackend_Media struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionTtl int32 `protobuf:"varint,1,opt,name=session_ttl,json=sessionTtl,proto3" json:"session_ttl,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminBackend_Media) Reset() { + *x = AdminBackend_Media{} + mi := &file_conf_conf_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminBackend_Media) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminBackend_Media) ProtoMessage() {} + +func (x *AdminBackend_Media) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[11] + 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 AdminBackend_Media.ProtoReflect.Descriptor instead. +func (*AdminBackend_Media) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{3, 4} +} + +func (x *AdminBackend_Media) GetSessionTtl() int32 { + if x != nil { + return x.SessionTtl + } + return 0 +} + type AdminBackend_Storage struct { state protoimpl.MessageState `protogen:"open.v1"` Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` @@ -905,7 +889,7 @@ func (x *AdminBackend_Storage) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminBackend_Storage.ProtoReflect.Descriptor instead. func (*AdminBackend_Storage) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{3, 4} + return file_conf_conf_proto_rawDescGZIP(), []int{3, 5} } func (x *AdminBackend_Storage) GetType() string { @@ -1004,7 +988,7 @@ func (x *AdminBackend_Qiniu) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminBackend_Qiniu.ProtoReflect.Descriptor instead. func (*AdminBackend_Qiniu) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{3, 5} + return file_conf_conf_proto_rawDescGZIP(), []int{3, 6} } func (x *AdminBackend_Qiniu) GetZone() string { @@ -1101,7 +1085,7 @@ func (x *AdminBackend_ObjectStore) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminBackend_ObjectStore.ProtoReflect.Descriptor instead. func (*AdminBackend_ObjectStore) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{3, 6} + return file_conf_conf_proto_rawDescGZIP(), []int{3, 7} } func (x *AdminBackend_ObjectStore) GetEndpoint() string { @@ -1183,17 +1167,12 @@ const file_conf_conf_proto_rawDesc = "" + "\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\x12.\n" + - "\x05admin\x18\x03 \x01(\v2\x18.kratos.api.AdminBackendR\x05admin\"\xb8\x02\n" + + "\x05admin\x18\x03 \x01(\v2\x18.kratos.api.AdminBackendR\x05admin\"\xa0\x01\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\x18\x01 \x01(\v2\x17.kratos.api.Server.HTTPR\x04http\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\"\xf6\x03\n" + "\x04Data\x125\n" + "\bdatabase\x18\x01 \x01(\v2\x19.kratos.api.Data.DatabaseR\bdatabase\x12,\n" + @@ -1212,7 +1191,7 @@ const file_conf_conf_proto_rawDesc = "" + "\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\fwriteTimeout\"\xd1\x0f\n" + + "\rwrite_timeout\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\fwriteTimeout\"\xb1\x10\n" + "\fAdminBackend\x12#\n" + "\rrouter_prefix\x18\x01 \x01(\tR\frouterPrefix\x12.\n" + "\x03jwt\x18\x02 \x01(\v2\x1c.kratos.api.AdminBackend.JWTR\x03jwt\x12:\n" + @@ -1221,7 +1200,8 @@ const file_conf_conf_proto_rawDesc = "" + "\x05email\x18\x05 \x01(\v2\x1e.kratos.api.AdminBackend.EmailR\x05email\x12:\n" + "\astorage\x18\x06 \x01(\v2 .kratos.api.AdminBackend.StorageR\astorage\x12\x1f\n" + "\vconfig_path\x18\a \x01(\tR\n" + - "configPath\x1a\xb8\x01\n" + + "configPath\x124\n" + + "\x05media\x18\b \x01(\v2\x1e.kratos.api.AdminBackend.MediaR\x05media\x1a\xb8\x01\n" + "\x03JWT\x12\x1f\n" + "\vsigning_key\x18\x01 \x01(\tR\n" + "signingKey\x12<\n" + @@ -1248,7 +1228,10 @@ const file_conf_conf_proto_rawDesc = "" + "\bnickname\x18\x05 \x01(\tR\bnickname\x12\x12\n" + "\x04port\x18\x06 \x01(\x05R\x04port\x12\x15\n" + "\x06is_ssl\x18\a \x01(\bR\x05isSsl\x12\"\n" + - "\ris_login_auth\x18\b \x01(\bR\visLoginAuth\x1a\xe8\x03\n" + + "\ris_login_auth\x18\b \x01(\bR\visLoginAuth\x1a(\n" + + "\x05Media\x12\x1f\n" + + "\vsession_ttl\x18\x01 \x01(\x05R\n" + + "sessionTtl\x1a\xe8\x03\n" + "\aStorage\x12\x12\n" + "\x04type\x18\x01 \x01(\tR\x04type\x124\n" + "\x05qiniu\x18\x02 \x01(\v2\x1e.kratos.api.AdminBackend.QiniuR\x05qiniu\x12C\n" + @@ -1307,13 +1290,13 @@ var file_conf_conf_proto_goTypes = []any{ (*Data)(nil), // 2: kratos.api.Data (*AdminBackend)(nil), // 3: kratos.api.AdminBackend (*Server_HTTP)(nil), // 4: kratos.api.Server.HTTP - (*Server_GRPC)(nil), // 5: kratos.api.Server.GRPC - (*Data_Database)(nil), // 6: kratos.api.Data.Database - (*Data_Redis)(nil), // 7: kratos.api.Data.Redis - (*AdminBackend_JWT)(nil), // 8: kratos.api.AdminBackend.JWT - (*AdminBackend_Captcha)(nil), // 9: kratos.api.AdminBackend.Captcha - (*AdminBackend_Local)(nil), // 10: kratos.api.AdminBackend.Local - (*AdminBackend_Email)(nil), // 11: kratos.api.AdminBackend.Email + (*Data_Database)(nil), // 5: kratos.api.Data.Database + (*Data_Redis)(nil), // 6: kratos.api.Data.Redis + (*AdminBackend_JWT)(nil), // 7: kratos.api.AdminBackend.JWT + (*AdminBackend_Captcha)(nil), // 8: kratos.api.AdminBackend.Captcha + (*AdminBackend_Local)(nil), // 9: kratos.api.AdminBackend.Local + (*AdminBackend_Email)(nil), // 10: kratos.api.AdminBackend.Email + (*AdminBackend_Media)(nil), // 11: kratos.api.AdminBackend.Media (*AdminBackend_Storage)(nil), // 12: kratos.api.AdminBackend.Storage (*AdminBackend_Qiniu)(nil), // 13: kratos.api.AdminBackend.Qiniu (*AdminBackend_ObjectStore)(nil), // 14: kratos.api.AdminBackend.ObjectStore @@ -1324,33 +1307,32 @@ var file_conf_conf_proto_depIdxs = []int32{ 2, // 1: kratos.api.Bootstrap.data:type_name -> kratos.api.Data 3, // 2: kratos.api.Bootstrap.admin:type_name -> kratos.api.AdminBackend 4, // 3: kratos.api.Server.http:type_name -> kratos.api.Server.HTTP - 5, // 4: kratos.api.Server.grpc:type_name -> kratos.api.Server.GRPC - 6, // 5: kratos.api.Data.database:type_name -> kratos.api.Data.Database - 7, // 6: kratos.api.Data.redis:type_name -> kratos.api.Data.Redis - 8, // 7: kratos.api.AdminBackend.jwt:type_name -> kratos.api.AdminBackend.JWT - 9, // 8: kratos.api.AdminBackend.captcha:type_name -> kratos.api.AdminBackend.Captcha - 10, // 9: kratos.api.AdminBackend.local:type_name -> kratos.api.AdminBackend.Local - 11, // 10: kratos.api.AdminBackend.email:type_name -> kratos.api.AdminBackend.Email - 12, // 11: kratos.api.AdminBackend.storage:type_name -> kratos.api.AdminBackend.Storage + 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.AdminBackend.jwt:type_name -> kratos.api.AdminBackend.JWT + 8, // 7: kratos.api.AdminBackend.captcha:type_name -> kratos.api.AdminBackend.Captcha + 9, // 8: kratos.api.AdminBackend.local:type_name -> kratos.api.AdminBackend.Local + 10, // 9: kratos.api.AdminBackend.email:type_name -> kratos.api.AdminBackend.Email + 12, // 10: kratos.api.AdminBackend.storage:type_name -> kratos.api.AdminBackend.Storage + 11, // 11: kratos.api.AdminBackend.media:type_name -> kratos.api.AdminBackend.Media 15, // 12: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration - 15, // 13: kratos.api.Server.GRPC.timeout:type_name -> google.protobuf.Duration - 15, // 14: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration - 15, // 15: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration - 15, // 16: kratos.api.AdminBackend.JWT.expires_time:type_name -> google.protobuf.Duration - 15, // 17: kratos.api.AdminBackend.JWT.buffer_time:type_name -> google.protobuf.Duration - 15, // 18: kratos.api.AdminBackend.Captcha.store_expiration:type_name -> google.protobuf.Duration - 13, // 19: kratos.api.AdminBackend.Storage.qiniu:type_name -> kratos.api.AdminBackend.Qiniu - 14, // 20: kratos.api.AdminBackend.Storage.aliyun_oss:type_name -> kratos.api.AdminBackend.ObjectStore - 14, // 21: kratos.api.AdminBackend.Storage.huawei_obs:type_name -> kratos.api.AdminBackend.ObjectStore - 14, // 22: kratos.api.AdminBackend.Storage.tencent_cos:type_name -> kratos.api.AdminBackend.ObjectStore - 14, // 23: kratos.api.AdminBackend.Storage.aws_s3:type_name -> kratos.api.AdminBackend.ObjectStore - 14, // 24: kratos.api.AdminBackend.Storage.cloudflare_r2:type_name -> kratos.api.AdminBackend.ObjectStore - 14, // 25: kratos.api.AdminBackend.Storage.minio:type_name -> kratos.api.AdminBackend.ObjectStore - 26, // [26:26] is the sub-list for method output_type - 26, // [26:26] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 15, // 13: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration + 15, // 14: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration + 15, // 15: kratos.api.AdminBackend.JWT.expires_time:type_name -> google.protobuf.Duration + 15, // 16: kratos.api.AdminBackend.JWT.buffer_time:type_name -> google.protobuf.Duration + 15, // 17: kratos.api.AdminBackend.Captcha.store_expiration:type_name -> google.protobuf.Duration + 13, // 18: kratos.api.AdminBackend.Storage.qiniu:type_name -> kratos.api.AdminBackend.Qiniu + 14, // 19: kratos.api.AdminBackend.Storage.aliyun_oss:type_name -> kratos.api.AdminBackend.ObjectStore + 14, // 20: kratos.api.AdminBackend.Storage.huawei_obs:type_name -> kratos.api.AdminBackend.ObjectStore + 14, // 21: kratos.api.AdminBackend.Storage.tencent_cos:type_name -> kratos.api.AdminBackend.ObjectStore + 14, // 22: kratos.api.AdminBackend.Storage.aws_s3:type_name -> kratos.api.AdminBackend.ObjectStore + 14, // 23: kratos.api.AdminBackend.Storage.cloudflare_r2:type_name -> kratos.api.AdminBackend.ObjectStore + 14, // 24: kratos.api.AdminBackend.Storage.minio:type_name -> kratos.api.AdminBackend.ObjectStore + 25, // [25:25] is the sub-list for method output_type + 25, // [25:25] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_conf_conf_proto_init() } diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto index 1a999c3..89e5419 100644 --- a/internal/conf/conf.proto +++ b/internal/conf/conf.proto @@ -17,13 +17,7 @@ message Server { 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 { @@ -58,6 +52,7 @@ message AdminBackend { Storage storage = 6; // ConfigPath is populated by the entrypoint and is not required in YAML. string config_path = 7; + Media media = 8; message JWT { string signing_key = 1; @@ -89,6 +84,10 @@ message AdminBackend { bool is_login_auth = 8; } + message Media { + int32 session_ttl = 1; + } + message Storage { string type = 1; Qiniu qiniu = 2; diff --git a/internal/data/access.go b/internal/data/access.go index 318888a..103341a 100644 --- a/internal/data/access.go +++ b/internal/data/access.go @@ -409,17 +409,101 @@ func menuFromPO(po menuPO) *biz.Menu { func menuToPO(v *biz.Menu) menuPO { return menuPO{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.ActiveName, KeepAlive: v.KeepAlive, DefaultMenu: v.DefaultMenu, Title: v.Title, Icon: v.Icon, CloseTab: v.CloseTab, TransitionType: v.TransitionType} } -func (r *accessRepo) CreateMenu(ctx context.Context, v *biz.Menu) error { - po := menuToPO(v) - if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { +func replaceMenuRelations(tx *gorm.DB, menu *biz.Menu) error { + var oldButtonIDs []uint + if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", menu.ID).Pluck("id", &oldButtonIDs).Error; err != nil { return err } - v.ID = po.ID + if len(oldButtonIDs) > 0 { + if err := tx.Where("sys_base_menu_btn_id IN ?", oldButtonIDs).Delete(&authorityButtonPO{}).Error; err != nil { + return err + } + } + if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuButtonPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuParameterPO{}).Error; err != nil { + return err + } + buttons := make([]menuButtonPO, 0, len(menu.Buttons)) + for _, button := range menu.Buttons { + buttons = append(buttons, menuButtonPO{Name: button.Name, Description: button.Description, MenuID: menu.ID}) + } + if len(buttons) > 0 { + if err := tx.Create(&buttons).Error; err != nil { + return err + } + } + parameters := make([]menuParameterPO, 0, len(menu.Parameters)) + for _, parameter := range menu.Parameters { + parameters = append(parameters, menuParameterPO{MenuID: menu.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + if len(parameters) > 0 { + return tx.Create(¶meters).Error + } return nil } +func (r *accessRepo) CreateMenu(ctx context.Context, v *biz.Menu) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var count int64 + if err := tx.Model(&menuPO{}).Where("name = ?", v.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("存在重复name,请修改name") + } + if v.ParentID != 0 { + var parent menuPO + if err := tx.First(&parent, v.ParentID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("父菜单不存在") + } + return err + } + if err := tx.Model(&menuPO{}).Where("parent_id = ?", v.ParentID).Count(&count).Error; err != nil { + return err + } + if count == 0 { + if err := tx.Model(&authorityPO{}).Where("default_router = ?", parent.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("父菜单已被其他角色的首页占用,请先释放父菜单的首页权限") + } + if err := tx.Where("sys_base_menu_id = ?", v.ParentID).Delete(&authorityMenuPO{}).Error; err != nil { + return err + } + } + } + po := menuToPO(v) + if err := tx.Create(&po).Error; err != nil { + return err + } + v.ID = po.ID + return replaceMenuRelations(tx, v) + }) +} func (r *accessRepo) UpdateMenu(ctx context.Context, v *biz.Menu) error { - po := menuToPO(v) - return r.data.gormDB.WithContext(ctx).Model(&menuPO{}).Where("id = ?", v.ID).Updates(&po).Error + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var old menuPO + if err := tx.First(&old, v.ID).Error; err != nil { + return err + } + var count int64 + if old.Name != v.Name { + if err := tx.Model(&menuPO{}).Where("id <> ? AND name = ?", v.ID, v.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("存在相同name修改失败") + } + } + if err := replaceMenuRelations(tx, v); err != nil { + return err + } + values := map[string]any{"parent_id": v.ParentID, "path": v.Path, "name": v.Name, "hidden": v.Hidden, "component": v.Component, "sort": v.Sort, "active_name": v.ActiveName, "keep_alive": v.KeepAlive, "default_menu": v.DefaultMenu, "title": v.Title, "icon": v.Icon, "close_tab": v.CloseTab, "transition_type": v.TransitionType} + return tx.Model(&old).Updates(values).Error + }) } func (r *accessRepo) DeleteMenu(ctx context.Context, id uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { @@ -430,6 +514,29 @@ func (r *accessRepo) DeleteMenu(ctx context.Context, id uint) error { if count > 0 { return errors.New("此菜单存在子菜单不可删除") } + var menu menuPO + if err := tx.First(&menu, id).Error; err != nil { + return errors.New("记录不存在") + } + if err := tx.Model(&authorityPO{}).Where("default_router = ?", menu.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("此菜单有角色正在作为首页,不可删除") + } + var buttonIDs []uint + if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", id).Pluck("id", &buttonIDs).Error; err != nil { + return err + } + if err := tx.Where("sys_menu_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuButtonPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuParameterPO{}).Error; err != nil { + return err + } if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { return err } @@ -441,7 +548,22 @@ func (r *accessRepo) FindMenu(ctx context.Context, id uint) (*biz.Menu, error) { if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } - return menuFromPO(po), nil + menu := menuFromPO(po) + if err := r.loadMenuParameters(ctx, menu); err != nil { + return nil, err + } + return menu, nil +} +func (r *accessRepo) loadMenuParameters(ctx context.Context, menu *biz.Menu) error { + var pos []menuParameterPO + if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Order("id").Find(&pos).Error; err != nil { + return err + } + menu.Parameters = make([]*biz.MenuParameter, 0, len(pos)) + for _, po := range pos { + menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: po.ID, MenuID: po.MenuID, Type: po.Type, Key: po.Key, Value: po.Value}) + } + return nil } func (r *accessRepo) ListMenus(ctx context.Context) ([]*biz.Menu, error) { var pos []menuPO @@ -450,7 +572,11 @@ func (r *accessRepo) ListMenus(ctx context.Context) ([]*biz.Menu, error) { } out := make([]*biz.Menu, 0, len(pos)) for _, po := range pos { - out = append(out, menuFromPO(po)) + menu := menuFromPO(po) + if err := r.loadMenuParameters(ctx, menu); err != nil { + return nil, err + } + out = append(out, menu) } return out, nil } @@ -760,6 +886,9 @@ func (r *accessRepo) CreateDepartment(ctx context.Context, v *biz.Department) er return r.data.gormDB.WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error } func (r *accessRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error { + if v.ParentID == v.ID { + return errors.New("父部门不能是自己") + } v.Ancestors = "0" if v.ParentID != 0 { var parent departmentPO @@ -771,12 +900,27 @@ func (r *accessRepo) UpdateDepartment(ctx context.Context, v *biz.Department) er return r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error } func (r *accessRepo) DeleteDepartment(ctx context.Context, id uint) error { + if id == 0 { + return errors.New("部门ID不能为空") + } var count int64 if err := r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { - return errors.New("部门存在子部门") + return errors.New("存在子部门,不允许删除") + } + if err := r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("dept_id = ?", id).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("该部门下存在用户,不允许删除") + } + if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("该部门下存在用户,不允许删除") } return r.data.gormDB.WithContext(ctx).Delete(&departmentPO{}, id).Error } @@ -820,7 +964,14 @@ func (r *accessRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint return r.replaceUserDepartments(ctx, id, ids) } func (r *accessRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error { + if id == 0 { + return errors.New("部门ID不能为空") + } return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var current []uint + if err := tx.Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", ¤t).Error; err != nil { + return err + } if err := tx.Where("sys_department_id = ?", id).Delete(&userDepartmentPO{}).Error; err != nil { return err } @@ -829,13 +980,39 @@ func (r *accessRepo) replaceUserDepartments(ctx context.Context, id uint, ids [] links = append(links, userDepartmentPO{UserID: uid, DepartmentID: id}) } if len(links) > 0 { - return tx.Create(&links).Error + if err := tx.Create(&links).Error; err != nil { + return err + } + } + target := make(map[uint]struct{}, len(ids)) + for _, uid := range ids { + target[uid] = struct{}{} + } + removed := make([]uint, 0) + for _, uid := range current { + if _, ok := target[uid]; !ok { + removed = append(removed, uid) + } + } + if len(removed) > 0 { + if err := tx.Model(&userPO{}).Where("dept_id = ? AND id IN ?", id, removed).Update("dept_id", 0).Error; err != nil { + return err + } + } + if len(ids) > 0 { + if err := tx.Model(&userPO{}).Where("dept_id = ? AND id IN ?", 0, ids).Update("dept_id", id).Error; err != nil { + return err + } } return nil }) } func (r *accessRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var user userPO + if err := tx.First(&user, uid).Error; err != nil { + return errors.New("查询用户数据失败") + } if err := tx.Where("sys_user_id = ?", uid).Delete(&userDepartmentPO{}).Error; err != nil { return err } @@ -851,6 +1028,18 @@ func (r *accessRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uin if primary == 0 && len(ids) > 0 { primary = ids[0] } + if primary != 0 { + inSet := false + for _, id := range ids { + if id == primary { + inSet = true + break + } + } + if !inSet { + return errors.New("主部门必须在归属部门范围内") + } + } return tx.Model(&userPO{}).Where("id = ?", uid).Update("dept_id", primary).Error }) } @@ -865,6 +1054,16 @@ func (r *accessRepo) UpdatePosition(ctx context.Context, v *biz.Position) error return r.data.gormDB.WithContext(ctx).Model(&positionPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}).Error } func (r *accessRepo) DeletePosition(ctx context.Context, id uint) error { + if id == 0 { + return errors.New("岗位ID不能为空") + } + var count int64 + if err := r.data.gormDB.WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("该岗位下存在用户,不允许删除") + } return r.data.gormDB.WithContext(ctx).Delete(&positionPO{}, id).Error } func (r *accessRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) { diff --git a/internal/data/admin.go b/internal/data/admin.go deleted file mode 100644 index fda25cf..0000000 --- a/internal/data/admin.go +++ /dev/null @@ -1,85 +0,0 @@ -package data - -import ( - "context" - "errors" - "time" - - "gorm.io/gorm" - "kra/internal/biz" -) - -type adminPO struct { - ID int64 `gorm:"primaryKey;autoIncrement"` - Name string `gorm:"uniqueIndex"` - Email string `gorm:"uniqueIndex"` - Password string - Access string - Avatar string - CreateTime time.Time - UpdateTime time.Time -} - -func (adminPO) TableName() string { return "admins" } -func convertAdmin(po *adminPO) *biz.Admin { - return &biz.Admin{ID: po.ID, Name: po.Name, Email: po.Email, Password: po.Password, Access: po.Access, Avatar: po.Avatar, CreateTime: po.CreateTime, UpdateTime: po.UpdateTime} -} - -type adminRepo struct{ data *Data } - -func NewAdminRepo(data *Data) biz.AdminRepo { return &adminRepo{data: data} } -func (r *adminRepo) one(ctx context.Context, query *gorm.DB) (*biz.Admin, error) { - var po adminPO - if err := query.WithContext(ctx).First(&po).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrAdminNotFound - } - return nil, err - } - return convertAdmin(&po), nil -} -func (r *adminRepo) FindByID(ctx context.Context, id int64) (*biz.Admin, error) { - return r.one(ctx, r.data.gormDB.Where("id = ?", id)) -} -func (r *adminRepo) FindByName(ctx context.Context, name string) (*biz.Admin, error) { - return r.one(ctx, r.data.gormDB.Where("name = ?", name)) -} -func (r *adminRepo) FindByEmail(ctx context.Context, email string) (*biz.Admin, error) { - return r.one(ctx, r.data.gormDB.Where("email = ?", email)) -} -func (r *adminRepo) ListAdmins(ctx context.Context, opts ...biz.ListOption) ([]*biz.Admin, error) { - o := biz.ListOptions{Limit: 20} - for _, opt := range opts { - opt(&o) - } - var pos []adminPO - if err := r.data.gormDB.WithContext(ctx).Offset(o.Offset).Limit(o.Limit).Order("id asc").Find(&pos).Error; err != nil { - return nil, err - } - out := make([]*biz.Admin, 0, len(pos)) - for i := range pos { - out = append(out, convertAdmin(&pos[i])) - } - return out, nil -} -func (r *adminRepo) CreateAdmin(ctx context.Context, a *biz.Admin) (*biz.Admin, error) { - now := time.Now() - po := adminPO{Name: a.Name, Email: a.Email, Password: a.Password, Access: a.Access, Avatar: a.Avatar, CreateTime: now, UpdateTime: now} - if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil { - return nil, err - } - return convertAdmin(&po), nil -} -func (r *adminRepo) UpdateAdmin(ctx context.Context, a *biz.Admin) (*biz.Admin, error) { - values := map[string]any{"name": a.Name, "email": a.Email, "access": a.Access, "avatar": a.Avatar, "update_time": time.Now()} - if a.Password != "" { - values["password"] = a.Password - } - if err := r.data.gormDB.WithContext(ctx).Model(&adminPO{}).Where("id = ?", a.ID).Updates(values).Error; err != nil { - return nil, err - } - return r.FindByID(ctx, a.ID) -} -func (r *adminRepo) DeleteAdmin(ctx context.Context, id int64) error { - return r.data.gormDB.WithContext(ctx).Delete(&adminPO{}, id).Error -} diff --git a/internal/data/aliyun_storage.go b/internal/data/aliyun_storage.go new file mode 100644 index 0000000..bb5d228 --- /dev/null +++ b/internal/data/aliyun_storage.go @@ -0,0 +1,105 @@ +package data + +import ( + "context" + "fmt" + "io" + "path" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "kra/internal/biz" + "kra/internal/conf" +) + +type aliyunStorage struct { + bucket *oss.Bucket + baseURL, prefix string +} + +func newAliyunStorage(config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { + if config == nil || config.Endpoint == "" || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { + return nil, fmt.Errorf("aliyun-oss storage configuration is incomplete") + } + client, err := oss.New(config.Endpoint, config.AccessKey, config.SecretKey) + if err != nil { + return nil, err + } + bucket, err := client.Bucket(config.Bucket) + if err != nil { + return nil, err + } + return &aliyunStorage{bucket: bucket, baseURL: strings.TrimSuffix(config.BaseUrl, "/"), prefix: strings.Trim(config.PathPrefix, "/")}, nil +} +func (s *aliyunStorage) key(name string) string { + if s.prefix == "" { + return strings.TrimPrefix(name, "/") + } + return path.Join(s.prefix, strings.TrimPrefix(name, "/")) +} +func (s *aliyunStorage) unkey(key string) string { + return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") +} +func (s *aliyunStorage) file(key string, size int64) *biz.StoredFile { + name := s.unkey(key) + url := s.baseURL + "/" + key + return &biz.StoredFile{Name: path.Base(name), Path: name, URL: url, Size: size} +} +func (s *aliyunStorage) Put(_ context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { + key := s.key(name) + if err := s.bucket.PutObject(key, reader); err != nil { + return nil, err + } + meta, err := s.bucket.GetObjectDetailedMeta(key) + if err != nil { + return nil, err + } + size := int64(0) + _, _ = fmt.Sscan(meta.Get("Content-Length"), &size) + return s.file(key, size), nil +} +func (s *aliyunStorage) Open(_ context.Context, name string) (io.ReadCloser, error) { + return s.bucket.GetObject(s.key(name)) +} +func (s *aliyunStorage) Delete(_ context.Context, name string) error { + return s.bucket.DeleteObject(s.key(name)) +} +func (s *aliyunStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { + return composeFiles(ctx, s, names, destination) +} +func (s *aliyunStorage) DeletePrefix(ctx context.Context, prefix string) error { + cursor := "" + for { + items, next, more, err := s.List(ctx, prefix, cursor, 1000) + if err != nil { + return err + } + for _, item := range items { + if err = s.Delete(ctx, item.Path); err != nil { + return err + } + } + if !more { + return nil + } + cursor = next + } +} +func (s *aliyunStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { + if limit < 1 || limit > 1000 { + limit = 100 + } + result, err := s.bucket.ListObjects(oss.Prefix(s.key(prefix)), oss.Marker(cursor), oss.MaxKeys(limit)) + if err != nil { + return nil, "", false, err + } + items := make([]*biz.StoredFile, 0, len(result.Objects)) + for _, object := range result.Objects { + items = append(items, s.file(object.Key, object.Size)) + } + next := "" + if result.IsTruncated { + next = result.NextMarker + } + return items, next, result.IsTruncated, nil +} diff --git a/internal/data/announcement.go b/internal/data/announcement.go index 25d93cc..2b54944 100644 --- a/internal/data/announcement.go +++ b/internal/data/announcement.go @@ -19,7 +19,7 @@ type announcementPO struct { Title string Content string `gorm:"type:text"` UserID *uint `gorm:"column:user_id"` - Attachments []byte `gorm:"type:json"` + Attachments jsonPO } func (announcementPO) TableName() string { return "kra_announcements_info" } @@ -33,7 +33,7 @@ func newAnnouncement(item *biz.Announcement) announcementPO { if len(attachments) == 0 || !json.Valid(attachments) { attachments = []byte("[]") } - return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: attachments} + return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: jsonPO(attachments)} } func announcementToBiz(item announcementPO) *biz.Announcement { diff --git a/internal/data/aws_storage.go b/internal/data/aws_storage.go new file mode 100644 index 0000000..dc8bae7 --- /dev/null +++ b/internal/data/aws_storage.go @@ -0,0 +1,152 @@ +package data + +import ( + "context" + "fmt" + "io" + "path" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + "kra/internal/biz" + "kra/internal/conf" +) + +type awsStorage struct { + client *s3.Client + bucket, baseURL, prefix string +} + +func newAWSStorage(provider string, config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { + if config == nil || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { + return nil, fmt.Errorf("%s storage configuration is incomplete", provider) + } + region := config.Region + endpoint := config.Endpoint + if provider == "cloudflare-r2" { + region = "auto" + if endpoint == "" { + if config.AccountId == "" { + return nil, fmt.Errorf("cloudflare-r2 account id is required") + } + endpoint = fmt.Sprintf("https://%s.r2.cloudflarestorage.com", config.AccountId) + } + } + if region == "" { + return nil, fmt.Errorf("%s region is required", provider) + } + loaded, err := awsconfig.LoadDefaultConfig(context.Background(), awsconfig.WithRegion(region), awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(config.AccessKey, config.SecretKey, ""))) + if err != nil { + return nil, err + } + client := s3.NewFromConfig(loaded, func(options *s3.Options) { + if endpoint != "" { + if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") { + if config.UseSsl { + endpoint = "https://" + endpoint + } else { + endpoint = "http://" + endpoint + } + } + options.BaseEndpoint = aws.String(endpoint) + } + options.UsePathStyle = config.ForcePathStyle + }) + return &awsStorage{client: client, bucket: config.Bucket, baseURL: strings.TrimSuffix(config.BaseUrl, "/"), prefix: strings.Trim(config.PathPrefix, "/")}, nil +} + +func (s *awsStorage) key(name string) string { + if s.prefix == "" { + return strings.TrimPrefix(name, "/") + } + return path.Join(s.prefix, strings.TrimPrefix(name, "/")) +} +func (s *awsStorage) unkey(key string) string { + return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") +} +func (s *awsStorage) file(key string, size int64) *biz.StoredFile { + name := s.unkey(key) + url := s.baseURL + "/" + key + return &biz.StoredFile{Name: path.Base(name), Path: name, URL: url, Size: size} +} +func (s *awsStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { + key := s.key(name) + result, err := manager.NewUploader(s.client).Upload(ctx, &s3.PutObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(key), Body: reader}) + if err != nil { + return nil, err + } + _ = result + head, err := s.client.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(key)}) + if err != nil { + return nil, err + } + size := int64(0) + if head.ContentLength != nil { + size = *head.ContentLength + } + return s.file(key, size), nil +} +func (s *awsStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) { + result, err := s.client.GetObject(ctx, &s3.GetObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(s.key(name))}) + if err != nil { + return nil, err + } + return result.Body, nil +} +func (s *awsStorage) Delete(ctx context.Context, name string) error { + _, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(s.key(name))}) + return err +} +func (s *awsStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { + return composeFiles(ctx, s, names, destination) +} +func (s *awsStorage) DeletePrefix(ctx context.Context, prefix string) error { + for { + items, _, more, err := s.List(ctx, prefix, "", 1000) + if err != nil { + return err + } + for _, item := range items { + if err = s.Delete(ctx, item.Path); err != nil { + return err + } + } + if !more || len(items) == 0 { + return nil + } + } +} +func (s *awsStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { + if limit < 1 || limit > 1000 { + limit = 100 + } + input := &s3.ListObjectsV2Input{Bucket: aws.String(s.bucket), Prefix: aws.String(s.key(prefix)), MaxKeys: aws.Int32(int32(limit))} + if cursor != "" { + input.ContinuationToken = aws.String(cursor) + } + result, err := s.client.ListObjectsV2(ctx, input) + if err != nil { + return nil, "", false, err + } + items := make([]*biz.StoredFile, 0, len(result.Contents)) + for _, object := range result.Contents { + if object.Key == nil { + continue + } + size := int64(0) + if object.Size != nil { + size = *object.Size + } + items = append(items, s.file(*object.Key, size)) + } + next := "" + if result.NextContinuationToken != nil { + next = *result.NextContinuationToken + } + more := result.IsTruncated != nil && *result.IsTruncated + return items, next, more, nil +} diff --git a/internal/data/cache.go b/internal/data/cache.go index 50b8ee1..2f69c8c 100644 --- a/internal/data/cache.go +++ b/internal/data/cache.go @@ -17,18 +17,24 @@ type memoryCacheEntry struct { } type cacheStore struct { - client *redis.Client + data *Data mu sync.Mutex memory map[string]memoryCacheEntry } func NewCache(data *Data) biz.Cache { - return &cacheStore{client: data.redis, memory: make(map[string]memoryCacheEntry)} + return &cacheStore{data: data, memory: make(map[string]memoryCacheEntry)} +} + +func (s *cacheStore) client() *redis.Client { + s.data.mu.RLock() + defer s.data.mu.RUnlock() + return s.data.redis } func (s *cacheStore) Get(ctx context.Context, key string) (string, bool, error) { - if s.client != nil { - value, err := s.client.Get(ctx, key).Result() + if client := s.client(); client != nil { + value, err := client.Get(ctx, key).Result() if err == nil { return value, true, nil } @@ -51,8 +57,8 @@ func (s *cacheStore) Get(ctx context.Context, key string) (string, bool, error) } func (s *cacheStore) Set(ctx context.Context, key, value string, expiration time.Duration) error { - if s.client != nil { - return s.client.Set(ctx, key, value, expiration).Err() + if client := s.client(); client != nil { + return client.Set(ctx, key, value, expiration).Err() } s.mu.Lock() defer s.mu.Unlock() @@ -65,8 +71,8 @@ func (s *cacheStore) Set(ctx context.Context, key, value string, expiration time } func (s *cacheStore) Delete(ctx context.Context, key string) error { - if s.client != nil { - return s.client.Del(ctx, key).Err() + if client := s.client(); client != nil { + return client.Del(ctx, key).Err() } s.mu.Lock() delete(s.memory, key) @@ -75,13 +81,13 @@ func (s *cacheStore) Delete(ctx context.Context, key string) error { } func (s *cacheStore) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) { - if s.client != nil { - value, err := s.client.Incr(ctx, key).Result() + if client := s.client(); client != nil { + value, err := client.Incr(ctx, key).Result() if err != nil { return 0, err } if value == 1 && expiration > 0 { - if err := s.client.Expire(ctx, key, expiration).Err(); err != nil { + if err := client.Expire(ctx, key, expiration).Err(); err != nil { return 0, err } } diff --git a/internal/data/config_store.go b/internal/data/config_store.go index b5f4184..5d6b041 100644 --- a/internal/data/config_store.go +++ b/internal/data/config_store.go @@ -1,11 +1,14 @@ package data import ( + "context" "encoding/json" "fmt" "os" "path/filepath" + "kra/internal/conf" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "gopkg.in/yaml.v3" @@ -50,6 +53,10 @@ func setYAMLMapping(node *yaml.Node, key string, value any) error { } func (d *Data) persistConfig() error { + return d.persistConfigValues(d.config, d.admin) +} + +func (d *Data) persistConfigValues(dataConfig *conf.Data, adminConfig *conf.AdminBackend) error { if d.admin == nil || d.admin.ConfigPath == "" { return nil } @@ -61,11 +68,11 @@ func (d *Data) persistConfig() error { if err = yaml.Unmarshal(raw, &document); err != nil { return err } - dataValue, err := protoMap(d.config) + dataValue, err := protoMap(dataConfig) if err != nil { return err } - adminValue, err := protoMap(d.admin) + adminValue, err := protoMap(adminConfig) if err != nil { return err } @@ -101,3 +108,77 @@ func (d *Data) persistConfig() error { } return os.Rename(tempName, d.admin.ConfigPath) } + +func (d *Data) reloadConfig(ctx context.Context) error { + if d.admin == nil || d.admin.ConfigPath == "" { + return fmt.Errorf("configuration path is not set") + } + raw, err := os.ReadFile(d.admin.ConfigPath) + if err != nil { + return err + } + var plain map[string]any + if err = yaml.Unmarshal(raw, &plain); err != nil { + return err + } + encoded, err := json.Marshal(plain) + if err != nil { + return err + } + var next conf.Bootstrap + if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(encoded, &next); err != nil { + return err + } + if next.Data == nil || next.Data.Database == nil || next.Admin == nil { + return fmt.Errorf("data.database and admin configuration are required") + } + next.Admin.ConfigPath = d.admin.ConfigPath + candidateDB, err := openDatabase(next.Data.Database, false, "") + if err != nil { + return fmt.Errorf("reload database: %w", err) + } + closeCandidate := true + defer func() { + if closeCandidate { + if sqlDB, closeErr := candidateDB.DB(); closeErr == nil { + _ = sqlDB.Close() + } + } + }() + if sqlDB, dbErr := candidateDB.DB(); dbErr != nil { + return dbErr + } else if err = sqlDB.PingContext(ctx); err != nil { + return fmt.Errorf("reload database: %w", err) + } + if err = migrateAll(candidateDB.WithContext(ctx)); err != nil { + return fmt.Errorf("reload database migrations: %w", err) + } + candidateStorage, err := buildFileStorage(next.Admin) + if err != nil { + return fmt.Errorf("reload storage: %w", err) + } + candidateRedis := openRedis(next.Data.Redis) + + d.mu.Lock() + oldDB, oldRedis := d.gormDB, d.redis + d.gormDB, d.redis = candidateDB, candidateRedis + proto.Reset(d.config) + proto.Merge(d.config, next.Data) + proto.Reset(d.admin) + proto.Merge(d.admin, next.Admin) + d.database = d.config.Database + d.mu.Unlock() + if d.storage != nil { + d.storage.replace(candidateStorage) + } + closeCandidate = false + if oldDB != nil { + if sqlDB, closeErr := oldDB.DB(); closeErr == nil { + _ = sqlDB.Close() + } + } + if oldRedis != nil { + _ = oldRedis.Close() + } + return nil +} diff --git a/internal/data/data.go b/internal/data/data.go index cb1fe67..18488cb 100644 --- a/internal/data/data.go +++ b/internal/data/data.go @@ -13,15 +13,17 @@ import ( "kra/internal/conf" ) -var ProviderSet = wire.NewSet(NewData, NewAdminRepo, NewSystemRepo, NewAccessRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) +var ProviderSet = wire.NewSet(NewData, NewSystemRepo, NewAccessRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) type Data struct { mu sync.RWMutex + initMu sync.Mutex gormDB *gorm.DB redis *redis.Client database *conf.Data_Database config *conf.Data admin *conf.AdminBackend + storage *reloadableStorage } func NewData(c *conf.Data, admin *conf.AdminBackend) (*Data, func(), error) { @@ -29,7 +31,8 @@ func NewData(c *conf.Data, admin *conf.AdminBackend) (*Data, func(), error) { return nil, nil, fmt.Errorf("database configuration is required") } d := &Data{database: c.Database, config: c, admin: admin} - db, err := openDatabase(c.Database, false) + db, err := openDatabase(c.Database, false, "") + usingFallback := false if err != nil { // The initialization endpoint must remain available when the configured // target database has not been created yet. @@ -38,28 +41,16 @@ func NewData(c *conf.Data, admin *conf.AdminBackend) (*Data, func(), error) { if err != nil { return nil, nil, fmt.Errorf("open bootstrap database: %w", err) } + usingFallback = true } d.gormDB = db - if err = db.AutoMigrate(&announcementPO{}, &adminPO{}); err != nil { - return nil, nil, fmt.Errorf("migrate bootstrap tables: %w", err) + if !usingFallback { + if err = migrateAll(db); err != nil { + return nil, nil, fmt.Errorf("migrate tables: %w", err) + } } - if c.Redis != nil && c.Redis.Addr != "" { - options := &redis.Options{Addr: c.Redis.Addr, Network: c.Redis.Network} - if c.Redis.ReadTimeout != nil { - options.ReadTimeout = c.Redis.ReadTimeout.AsDuration() - } - if c.Redis.WriteTimeout != nil { - options.WriteTimeout = c.Redis.WriteTimeout.AsDuration() - } - candidate := redis.NewClient(options) - pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond) - if pingErr := candidate.Ping(pingCtx).Err(); pingErr != nil { - log.Printf("redis unavailable, using in-memory cache: %v", pingErr) - _ = candidate.Close() - } else { - d.redis = candidate - } - cancel() + if candidate := openRedis(c.Redis); candidate != nil { + d.redis = candidate } cleanup := func() { d.mu.RLock() @@ -75,24 +66,38 @@ func NewData(c *conf.Data, admin *conf.AdminBackend) (*Data, func(), error) { return d, cleanup, nil } -func (d *Data) switchDatabase(config *conf.Data_Database) error { - db, err := openDatabase(config, true) - if err != nil { - return err +func openRedis(config *conf.Data_Redis) *redis.Client { + if config == nil || config.Addr == "" { + return nil } + options := &redis.Options{Addr: config.Addr, Network: config.Network} + if config.ReadTimeout != nil { + options.ReadTimeout = config.ReadTimeout.AsDuration() + } + if config.WriteTimeout != nil { + options.WriteTimeout = config.WriteTimeout.AsDuration() + } + candidate := redis.NewClient(options) + pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond) + defer cancel() + if err := candidate.Ping(pingCtx).Err(); err != nil { + log.Printf("redis unavailable, using in-memory cache: %v", err) + _ = candidate.Close() + return nil + } + return candidate +} + +func (d *Data) activateDatabase(db *gorm.DB, config *conf.Data_Database) { d.mu.Lock() old := d.gormDB d.gormDB = db d.database = config d.config.Database = config d.mu.Unlock() - if err := d.persistConfig(); err != nil { - return fmt.Errorf("persist database configuration: %w", err) - } if old != nil { if sqlDB, e := old.DB(); e == nil { _ = sqlDB.Close() } } - return nil } diff --git a/internal/data/database.go b/internal/data/database.go index 61c639e..b89a82f 100644 --- a/internal/data/database.go +++ b/internal/data/database.go @@ -30,8 +30,10 @@ func normalizedDriver(driver string) string { return "sqlite" case "oracle": return "oracle" - default: + case "", "mysql": return "mysql" + default: + return "" } } @@ -114,8 +116,11 @@ func openWithDriver(driver, dsn string) (*gorm.DB, error) { } } -func openDatabase(c *conf.Data_Database, create bool) (*gorm.DB, error) { +func openDatabase(c *conf.Data_Database, create bool, template string) (*gorm.DB, error) { driver := normalizedDriver(c.Driver) + if driver == "" { + return nil, fmt.Errorf("unsupported database driver %q", c.Driver) + } if driver == "sqlite" { dsn, err := databaseDSN(c, "") if err != nil { @@ -150,9 +155,15 @@ func openDatabase(c *conf.Data_Database, create bool) (*gorm.DB, error) { case "mysql": statement = "CREATE DATABASE IF NOT EXISTS `" + c.Name + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" case "pgsql": + if template == "" { + template = "template0" + } + if !databaseNamePattern.MatchString(template) { + return nil, fmt.Errorf("invalid PostgreSQL template %q", template) + } var count int64 if err = adminDB.Raw("SELECT count(*) FROM pg_database WHERE datname = ?", c.Name).Scan(&count).Error; err == nil && count == 0 { - statement = `CREATE DATABASE "` + c.Name + `"` + statement = `CREATE DATABASE "` + c.Name + `" TEMPLATE "` + template + `"` } case "mssql": statement = "IF DB_ID(N'" + c.Name + "') IS NULL CREATE DATABASE [" + c.Name + "]" diff --git a/internal/data/email.go b/internal/data/email.go index 788d118..712946b 100644 --- a/internal/data/email.go +++ b/internal/data/email.go @@ -17,25 +17,34 @@ import ( ) type emailRepo struct { - config *conf.AdminBackend_Email + config *conf.AdminBackend } func NewEmailRepo(config *conf.AdminBackend) biz.EmailRepo { if config == nil { return &emailRepo{} } - return &emailRepo{config: config.Email} + return &emailRepo{config: config} } -func (r *emailRepo) Enabled() bool { - return r.config != nil && r.config.Host != "" && r.config.From != "" && r.config.Secret != "" && r.config.Port > 0 -} - -func (r *emailRepo) DefaultRecipients() []string { +func (r *emailRepo) email() *conf.AdminBackend_Email { if r.config == nil { return nil } - parts := strings.Split(r.config.To, ",") + return r.config.Email +} + +func (r *emailRepo) Enabled() bool { + config := r.email() + return config != nil && config.Host != "" && config.From != "" && config.Secret != "" && config.Port > 0 +} + +func (r *emailRepo) DefaultRecipients() []string { + config := r.email() + if config == nil { + return nil + } + parts := strings.Split(config.To, ",") result := make([]string, 0, len(parts)) for _, part := range parts { if recipient := strings.TrimSpace(part); recipient != "" { @@ -56,7 +65,7 @@ func (r *emailRepo) Send(ctx context.Context, to []string, subject, body string) if len(to) == 0 { return errors.New("收件人不能为空") } - config := r.config + config := r.email() address := net.JoinHostPort(config.Host, fmt.Sprint(config.Port)) dialer := &net.Dialer{Timeout: 10 * time.Second} var conn net.Conn diff --git a/internal/data/ent/admin.go b/internal/data/ent/admin.go deleted file mode 100644 index 3a98dc3..0000000 --- a/internal/data/ent/admin.go +++ /dev/null @@ -1,172 +0,0 @@ -// 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 deleted file mode 100644 index 767c0ee..0000000 --- a/internal/data/ent/admin/admin.go +++ /dev/null @@ -1,116 +0,0 @@ -// 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 deleted file mode 100644 index 42c98e2..0000000 --- a/internal/data/ent/admin/where.go +++ /dev/null @@ -1,510 +0,0 @@ -// 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 deleted file mode 100644 index a9fc110..0000000 --- a/internal/data/ent/admin_create.go +++ /dev/null @@ -1,859 +0,0 @@ -// 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 deleted file mode 100644 index 881482f..0000000 --- a/internal/data/ent/admin_delete.go +++ /dev/null @@ -1,88 +0,0 @@ -// 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 deleted file mode 100644 index 4192c01..0000000 --- a/internal/data/ent/admin_query.go +++ /dev/null @@ -1,527 +0,0 @@ -// 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 deleted file mode 100644 index ca468b8..0000000 --- a/internal/data/ent/admin_update.go +++ /dev/null @@ -1,382 +0,0 @@ -// 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 deleted file mode 100644 index a363e2d..0000000 --- a/internal/data/ent/client.go +++ /dev/null @@ -1,366 +0,0 @@ -// 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 deleted file mode 100644 index 1976d56..0000000 --- a/internal/data/ent/ent.go +++ /dev/null @@ -1,608 +0,0 @@ -// 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 deleted file mode 100644 index 6b9daaf..0000000 --- a/internal/data/ent/enttest/enttest.go +++ /dev/null @@ -1,84 +0,0 @@ -// 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 deleted file mode 100644 index 67985fa..0000000 --- a/internal/data/ent/generate.go +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index cb9a040..0000000 --- a/internal/data/ent/hook/hook.go +++ /dev/null @@ -1,199 +0,0 @@ -// 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 deleted file mode 100644 index 1956a6b..0000000 --- a/internal/data/ent/migrate/migrate.go +++ /dev/null @@ -1,64 +0,0 @@ -// 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 deleted file mode 100644 index c140ea5..0000000 --- a/internal/data/ent/migrate/schema.go +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100644 index 9ee90fa..0000000 --- a/internal/data/ent/mutation.go +++ /dev/null @@ -1,684 +0,0 @@ -// 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 deleted file mode 100644 index 1c3f480..0000000 --- a/internal/data/ent/predicate/predicate.go +++ /dev/null @@ -1,10 +0,0 @@ -// 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 deleted file mode 100644 index 82d5dc5..0000000 --- a/internal/data/ent/runtime.go +++ /dev/null @@ -1,48 +0,0 @@ -// 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 deleted file mode 100644 index 3f91d65..0000000 --- a/internal/data/ent/runtime/runtime.go +++ /dev/null @@ -1,10 +0,0 @@ -// 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 deleted file mode 100644 index ad07e95..0000000 --- a/internal/data/ent/schema/admin.go +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 2789ff3..0000000 --- a/internal/data/ent/tx.go +++ /dev/null @@ -1,236 +0,0 @@ -// 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/data/file_storage.go b/internal/data/file_storage.go index 9635c4e..792f6b2 100644 --- a/internal/data/file_storage.go +++ b/internal/data/file_storage.go @@ -10,6 +10,7 @@ import ( "path/filepath" "sort" "strings" + "sync" "kra/internal/biz" "kra/internal/conf" @@ -20,7 +21,22 @@ type fileStorage struct { urlPrefix string } -func NewFileStorage(config *conf.AdminBackend) (biz.FileStorage, error) { +type reloadableStorage struct { + mu sync.RWMutex + current biz.FileStorage +} + +func NewFileStorage(data *Data) (biz.FileStorage, error) { + storage, err := buildFileStorage(data.admin) + if err != nil { + return nil, err + } + wrapper := &reloadableStorage{current: storage} + data.storage = wrapper + return wrapper, nil +} + +func buildFileStorage(config *conf.AdminBackend) (biz.FileStorage, error) { storageType := "local" if config != nil && config.Storage != nil && config.Storage.Type != "" { storageType = strings.ToLower(config.Storage.Type) @@ -32,15 +48,15 @@ func NewFileStorage(config *conf.AdminBackend) (biz.FileStorage, error) { var object *conf.AdminBackend_ObjectStore switch storageType { case "aliyun-oss": - object = config.Storage.AliyunOss + return newAliyunStorage(config.Storage.AliyunOss) case "huawei-obs": - object = config.Storage.HuaweiObs + return newHuaweiStorage(config.Storage.HuaweiObs) case "tencent-cos": - object = config.Storage.TencentCos + return newTencentStorage(config.Storage.TencentCos) case "aws-s3": - object = config.Storage.AwsS3 + return newAWSStorage(storageType, config.Storage.AwsS3) case "cloudflare-r2": - object = config.Storage.CloudflareR2 + return newAWSStorage(storageType, config.Storage.CloudflareR2) case "minio": object = config.Storage.Minio default: @@ -64,6 +80,42 @@ func NewFileStorage(config *conf.AdminBackend) (biz.FileStorage, error) { return &fileStorage{root: root, urlPrefix: "/" + strings.Trim(prefix, "/")}, nil } +func (s *reloadableStorage) replace(storage biz.FileStorage) { + s.mu.Lock() + s.current = storage + s.mu.Unlock() +} +func (s *reloadableStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.current.Put(ctx, name, reader) +} +func (s *reloadableStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.current.Open(ctx, name) +} +func (s *reloadableStorage) Delete(ctx context.Context, name string) error { + s.mu.RLock() + defer s.mu.RUnlock() + return s.current.Delete(ctx, name) +} +func (s *reloadableStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.current.Compose(ctx, names, destination) +} +func (s *reloadableStorage) DeletePrefix(ctx context.Context, prefix string) error { + s.mu.RLock() + defer s.mu.RUnlock() + return s.current.DeletePrefix(ctx, prefix) +} +func (s *reloadableStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.current.List(ctx, prefix, cursor, limit) +} + func composeFiles(ctx context.Context, storage biz.FileStorage, names []string, destination string) (*biz.StoredFile, string, error) { reader, writer := io.Pipe() hash := md5.New() diff --git a/internal/data/huawei_storage.go b/internal/data/huawei_storage.go new file mode 100644 index 0000000..2b26f65 --- /dev/null +++ b/internal/data/huawei_storage.go @@ -0,0 +1,104 @@ +package data + +import ( + "context" + "fmt" + "io" + "path" + "strings" + + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "kra/internal/biz" + "kra/internal/conf" +) + +type huaweiStorage struct { + client *obs.ObsClient + bucket, baseURL, prefix string +} + +func newHuaweiStorage(config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { + if config == nil || config.Endpoint == "" || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { + return nil, fmt.Errorf("huawei-obs storage configuration is incomplete") + } + client, err := obs.New(config.AccessKey, config.SecretKey, config.Endpoint) + if err != nil { + return nil, err + } + return &huaweiStorage{client: client, bucket: config.Bucket, baseURL: strings.TrimSuffix(config.BaseUrl, "/"), prefix: strings.Trim(config.PathPrefix, "/")}, nil +} +func (s *huaweiStorage) key(name string) string { + if s.prefix == "" { + return strings.TrimPrefix(name, "/") + } + return path.Join(s.prefix, strings.TrimPrefix(name, "/")) +} +func (s *huaweiStorage) unkey(key string) string { + return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") +} +func (s *huaweiStorage) file(key string, size int64) *biz.StoredFile { + name := s.unkey(key) + fileURL := s.baseURL + "/" + key + return &biz.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size} +} +func (s *huaweiStorage) Put(_ context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { + key := s.key(name) + if _, err := s.client.PutObject(&obs.PutObjectInput{PutObjectBasicInput: obs.PutObjectBasicInput{ObjectOperationInput: obs.ObjectOperationInput{Bucket: s.bucket, Key: key}}, Body: reader}); err != nil { + return nil, err + } + meta, err := s.client.GetObjectMetadata(&obs.GetObjectMetadataInput{Bucket: s.bucket, Key: key}) + if err != nil { + return nil, err + } + return s.file(key, meta.ContentLength), nil +} +func (s *huaweiStorage) Open(_ context.Context, name string) (io.ReadCloser, error) { + result, err := s.client.GetObject(&obs.GetObjectInput{GetObjectMetadataInput: obs.GetObjectMetadataInput{Bucket: s.bucket, Key: s.key(name)}}) + if err != nil { + return nil, err + } + return result.Body, nil +} +func (s *huaweiStorage) Delete(_ context.Context, name string) error { + _, err := s.client.DeleteObject(&obs.DeleteObjectInput{Bucket: s.bucket, Key: s.key(name)}) + return err +} +func (s *huaweiStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { + return composeFiles(ctx, s, names, destination) +} +func (s *huaweiStorage) DeletePrefix(ctx context.Context, prefix string) error { + cursor := "" + for { + items, next, more, err := s.List(ctx, prefix, cursor, 1000) + if err != nil { + return err + } + for _, item := range items { + if err = s.Delete(ctx, item.Path); err != nil { + return err + } + } + if !more { + return nil + } + cursor = next + } +} +func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { + if limit < 1 || limit > 1000 { + limit = 100 + } + result, err := s.client.ListObjects(&obs.ListObjectsInput{ListObjsInput: obs.ListObjsInput{Prefix: s.key(prefix), MaxKeys: limit}, Bucket: s.bucket, Marker: cursor}) + if err != nil { + return nil, "", false, err + } + items := make([]*biz.StoredFile, 0, len(result.Contents)) + for _, object := range result.Contents { + items = append(items, s.file(object.Key, object.Size)) + } + next := result.NextMarker + if result.IsTruncated && next == "" && len(result.Contents) > 0 { + next = result.Contents[len(result.Contents)-1].Key + } + return items, next, result.IsTruncated, nil +} diff --git a/internal/data/json_type.go b/internal/data/json_type.go new file mode 100644 index 0000000..b86b760 --- /dev/null +++ b/internal/data/json_type.go @@ -0,0 +1,48 @@ +package data + +import ( + "database/sql/driver" + "fmt" + + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +// jsonPO selects a native or textual JSON storage type supported by each +// configured database while keeping one value shape in the data layer. +type jsonPO []byte + +func (jsonPO) GormDataType() string { return "json" } +func (jsonPO) GormDBDataType(db *gorm.DB, _ *schema.Field) string { + switch db.Dialector.Name() { + case "mysql", "sqlite": + return "JSON" + case "postgres": + return "JSONB" + case "sqlserver": + return "NVARCHAR(MAX)" + case "oracle": + return "CLOB" + default: + return "TEXT" + } +} +func (j jsonPO) Value() (driver.Value, error) { + if len(j) == 0 { + return nil, nil + } + return string(j), nil +} +func (j *jsonPO) Scan(value any) error { + switch raw := value.(type) { + case nil: + *j = nil + case []byte: + *j = append((*j)[:0], raw...) + case string: + *j = append((*j)[:0], raw...) + default: + return fmt.Errorf("cannot scan JSON from %T", value) + } + return nil +} diff --git a/internal/data/media.go b/internal/data/media.go index 0120b5a..f78917f 100644 --- a/internal/data/media.go +++ b/internal/data/media.go @@ -258,3 +258,16 @@ func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.Uploa func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error { return r.data.gormDB.WithContext(ctx).Unscoped().Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error } +func (r *mediaRepo) StaleUploadSessionIDs(ctx context.Context, before time.Time) ([]uint, error) { + var ids []uint + err := r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("status = ? AND updated_at < ?", "uploading", before).Pluck("id", &ids).Error + return ids, err +} +func (r *mediaRepo) DeleteUploadData(ctx context.Context, uploadID uint) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := tx.Unscoped().Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error; err != nil { + return err + } + return tx.Unscoped().Delete(&uploadSessionPO{}, uploadID).Error + }) +} diff --git a/internal/data/migrations.go b/internal/data/migrations.go new file mode 100644 index 0000000..cf70f9e --- /dev/null +++ b/internal/data/migrations.go @@ -0,0 +1,16 @@ +package data + +import "gorm.io/gorm" + +func migrateAll(db *gorm.DB) error { + return db.AutoMigrate( + &userPO{}, &authorityPO{}, &menuPO{}, &userAuthorityPO{}, &authorityMenuPO{}, &menuParameterPO{}, + &apiPO{}, &ignoredAPIPO{}, &authorityAPIPO{}, &menuButtonPO{}, &authorityButtonPO{}, + &departmentPO{}, &positionPO{}, &userDepartmentPO{}, &userPositionPO{}, &authorityDepartmentPO{}, + &dictionaryPO{}, &dictionaryDetailPO{}, ¶meterPO{}, &apiTokenPO{}, &jwtBlacklistPO{}, &securityConfigPO{}, + &versionPO{}, &exportTemplatePO{}, &exportConditionPO{}, &exportJoinPO{}, + &operationPO{}, &loginLogPO{}, &dataAccessLogPO{}, &errorRecordPO{}, + &taskPO{}, &taskLogPO{}, &mediaPO{}, &categoryPO{}, &uploadSessionPO{}, &uploadChunkPO{}, + &announcementPO{}, + ) +} diff --git a/internal/data/s3_storage.go b/internal/data/s3_storage.go index fe55e2c..30a39c9 100644 --- a/internal/data/s3_storage.go +++ b/internal/data/s3_storage.go @@ -69,9 +69,6 @@ func (s *s3Storage) unkey(key string) string { func (s *s3Storage) file(key string, size int64) *biz.StoredFile { name := s.unkey(key) rawURL := s.baseURL + "/" + key - if s.baseURL == "" { - rawURL = key - } return &biz.StoredFile{Name: path.Base(name), Path: name, URL: rawURL, Size: size} } func (s *s3Storage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { diff --git a/internal/data/settings.go b/internal/data/settings.go index de5596e..e84b4c2 100644 --- a/internal/data/settings.go +++ b/internal/data/settings.go @@ -73,6 +73,16 @@ type apiTokenPO struct { func (apiTokenPO) TableName() string { return "sys_api_tokens" } +type jwtBlacklistPO struct { + ID uint `gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` + JWT string `gorm:"column:jwt;type:text"` +} + +func (jwtBlacklistPO) TableName() string { return "jwt_blacklists" } + type securityConfigPO struct { ID uint `gorm:"primaryKey"` CaptchaOpen int @@ -435,15 +445,28 @@ func (r *settingsRepo) DisableAPIToken(ctx context.Context, id uint) (string, er } return po.Token, r.data.gormDB.WithContext(ctx).Model(&po).Update("status", false).Error } -func (r *settingsRepo) IsTokenDisabled(ctx context.Context, token string) (bool, error) { - var po apiTokenPO - err := r.data.gormDB.WithContext(ctx).Where("token = ?", token).First(&po).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - return false, nil +func (r *settingsRepo) BlacklistToken(ctx context.Context, token string) error { + if token == "" { + return errors.New("token is empty") } - if err != nil { + return r.data.gormDB.WithContext(ctx).Create(&jwtBlacklistPO{JWT: token}).Error +} +func (r *settingsRepo) IsTokenDisabled(ctx context.Context, token string) (bool, error) { + var blacklistCount int64 + if err := r.data.gormDB.WithContext(ctx).Model(&jwtBlacklistPO{}).Where("jwt = ?", token).Count(&blacklistCount).Error; err != nil { return false, err } + if blacklistCount > 0 { + return true, nil + } + var po apiTokenPO + result := r.data.gormDB.WithContext(ctx).Where("token = ?", token).Limit(1).Find(&po) + if result.Error != nil { + return false, result.Error + } + if result.RowsAffected == 0 { + return false, nil + } return !po.Status || time.Now().After(po.ExpiresAt), nil } diff --git a/internal/data/system.go b/internal/data/system.go index 6b7f6d8..2856d0c 100644 --- a/internal/data/system.go +++ b/internal/data/system.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "errors" + "fmt" + "strings" "time" "kra/internal/biz" @@ -11,6 +13,8 @@ import ( "github.com/google/uuid" "golang.org/x/crypto/bcrypt" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "gorm.io/gorm" ) @@ -86,28 +90,72 @@ type authorityMenuPO struct { func (authorityMenuPO) TableName() string { return "sys_authority_menus" } +type menuParameterPO struct { + ID uint `gorm:"primaryKey"` + MenuID uint `gorm:"column:sys_base_menu_id;index"` + Type string + Key string + Value string +} + +func (menuParameterPO) TableName() string { return "sys_base_menu_parameters" } + type systemRepo struct{ data *Data } func NewSystemRepo(data *Data) biz.SystemRepo { return &systemRepo{data: data} } func (r *systemRepo) PersistConfig(context.Context) error { return r.data.persistConfig() } +func (r *systemRepo) PersistAdminConfig(_ context.Context, raw []byte) error { + next := proto.Clone(r.data.admin).(*conf.AdminBackend) + if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(raw, next); err != nil { + return err + } + next.ConfigPath = r.data.admin.ConfigPath + return r.data.persistConfigValues(r.data.config, next) +} +func (r *systemRepo) ReloadConfig(ctx context.Context) error { + return r.data.reloadConfig(ctx) +} func (r *systemRepo) IsInitialized(ctx context.Context) (bool, error) { - return r.data.gormDB.WithContext(ctx).Migrator().HasTable(&userPO{}), nil + db := r.data.gormDB.WithContext(ctx) + if !db.Migrator().HasTable(&userPO{}) { + return false, nil + } + var count int64 + if err := db.Model(&userPO{}).Where("username = ?", "admin").Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil } func (r *systemRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error { config := &conf.Data_Database{Driver: input.Driver, Host: input.Host, Port: input.Port, User: input.User, Password: input.Password, Name: input.Name, Path: input.Path, Config: input.Config} - if err := r.data.switchDatabase(config); err != nil { + r.data.initMu.Lock() + defer r.data.initMu.Unlock() + initialized, err := r.IsInitialized(ctx) + if err != nil { return err } - db := r.data.gormDB.WithContext(ctx) - if err := db.AutoMigrate(&adminPO{}); err != nil { + if initialized { + return errors.New("数据库已初始化,无需重复初始化") + } + candidate, err := openDatabase(config, true, input.Template) + if err != nil { return err } - if err := db.AutoMigrate(&userPO{}, &authorityPO{}, &menuPO{}, &userAuthorityPO{}, &authorityMenuPO{}, &apiPO{}, &ignoredAPIPO{}, &authorityAPIPO{}, &menuButtonPO{}, &authorityButtonPO{}, &departmentPO{}, &positionPO{}, &userDepartmentPO{}, &userPositionPO{}, &authorityDepartmentPO{}, &dictionaryPO{}, &dictionaryDetailPO{}, ¶meterPO{}, &apiTokenPO{}, &securityConfigPO{}, &versionPO{}, &exportTemplatePO{}, &exportConditionPO{}, &exportJoinPO{}, &operationPO{}, &loginLogPO{}, &dataAccessLogPO{}, &errorRecordPO{}, &taskPO{}, &taskLogPO{}, &mediaPO{}, &categoryPO{}, &uploadSessionPO{}, &uploadChunkPO{}, &announcementPO{}); err != nil { + activated := false + defer func() { + if !activated { + if sqlDB, closeErr := candidate.DB(); closeErr == nil { + _ = sqlDB.Close() + } + } + }() + db := candidate.WithContext(ctx) + if err := migrateAll(db); err != nil { return err } - return db.Transaction(func(tx *gorm.DB) error { + if err := db.Transaction(func(tx *gorm.DB) error { authority := authorityPO{AuthorityID: 888, AuthorityName: "超级管理员", DataScope: 1, DefaultRouter: "dashboard"} if err := tx.FirstOrCreate(&authority, authorityPO{AuthorityID: 888}).Error; err != nil { return err @@ -127,10 +175,12 @@ func (r *systemRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) nameID[menu.Name] = menu.ID } for i := range menus { - if menus[i].ParentID == 0 || menus[i].ActiveName == "" { + if menus[i].ActiveName == "" { continue } - _ = tx.Model(&menuPO{}).Where("name = ?", menus[i].Name).Updates(map[string]any{"parent_id": nameID[menus[i].ActiveName], "active_name": ""}).Error + if err := tx.Model(&menuPO{}).Where("name = ?", menus[i].Name).Updates(map[string]any{"parent_id": nameID[menus[i].ActiveName], "active_name": ""}).Error; err != nil { + return err + } } if err := tx.Where("sys_authority_authority_id = ?", 888).Delete(&authorityMenuPO{}).Error; err != nil { return err @@ -162,15 +212,56 @@ func (r *systemRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) return err } } + department := departmentPO{Name: "总公司", ParentID: 0, Ancestors: "0", Sort: 0, Status: true} + if err := tx.Where("name = ?", department.Name).FirstOrCreate(&department).Error; err != nil { + return err + } + for _, position := range []positionPO{{Name: "总经理", Code: "CEO", Sort: 1, Status: true}, {Name: "普通员工", Code: "STAFF", Sort: 2, Status: true}} { + if err := tx.Where("code = ?", position.Code).FirstOrCreate(&position).Error; err != nil { + return err + } + } + security := defaultSecurityConfig() + if err := tx.FirstOrCreate(&security, securityConfigPO{ID: 1}).Error; err != nil { + return err + } + exportTemplate := exportTemplatePO{Name: "api", DBTableName: "sys_apis", TemplateID: "api", TemplateInfo: "{\n\"path\":\"路径\",\n\"method\":\"方法(大写)\",\n\"description\":\"方法介绍\",\n\"api_group\":\"方法分组\"\n}"} + if err := tx.Where("template_id = ?", exportTemplate.TemplateID).FirstOrCreate(&exportTemplate).Error; err != nil { + return err + } + for _, task := range []taskPO{{Name: "ClearDB", Description: "定时清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Spec: "@daily", ExecutorType: "method", MethodName: "ClearDB", Enabled: true}, {Name: "CleanStaleUploads", Description: "定时清理过期大文件上传会话", Spec: "@hourly", ExecutorType: "method", MethodName: "CleanStaleUploads", Enabled: true}} { + if err := tx.Where("name = ?", task.Name).FirstOrCreate(&task).Error; err != nil { + return err + } + } + for _, item := range input.APIs { + if item == nil { + continue + } + po := apiPO{Path: item.Path, Method: strings.ToUpper(item.Method), Description: item.Description, APIGroup: item.APIGroup} + if err := tx.Where("path = ? AND method = ?", po.Path, po.Method).FirstOrCreate(&po).Error; err != nil { + return err + } + } return nil - }) + }); err != nil { + return err + } + nextData := proto.Clone(r.data.config).(*conf.Data) + nextData.Database = config + if err := r.data.persistConfigValues(nextData, r.data.admin); err != nil { + return fmt.Errorf("persist database configuration: %w", err) + } + r.data.activateDatabase(candidate, config) + activated = true + return nil } func (r *systemRepo) FindUserByUsername(ctx context.Context, username string) (*biz.User, error) { var po userPO if err := r.data.gormDB.WithContext(ctx).Where("username = ?", username).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrAdminNotFound + return nil, biz.ErrUserNotFound } return nil, err } @@ -181,7 +272,7 @@ func (r *systemRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, erro var po userPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrAdminNotFound + return nil, biz.ErrUserNotFound } return nil, err } @@ -245,7 +336,22 @@ func (r *systemRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([] } byID := make(map[uint]*biz.Menu, len(pos)) for _, po := range pos { - byID[po.ID] = &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType, Children: []*biz.Menu{}} + menu := &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType, Children: []*biz.Menu{}} + var parameters []menuParameterPO + if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", po.ID).Order("id").Find(¶meters).Error; err != nil { + return nil, err + } + for _, parameter := range parameters { + menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + var buttons []menuButtonPO + if err := r.data.gormDB.WithContext(ctx).Table("sys_base_menu_btns").Joins("JOIN sys_authority_btns ON sys_authority_btns.sys_base_menu_btn_id = sys_base_menu_btns.id").Where("sys_authority_btns.authority_id = ? AND sys_authority_btns.sys_menu_id = ?", authorityID, po.ID).Find(&buttons).Error; err != nil { + return nil, err + } + for _, button := range buttons { + menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: button.MenuID, AuthorityID: authorityID}) + } + byID[po.ID] = menu } roots := make([]*biz.Menu, 0) for _, po := range pos { @@ -355,6 +461,12 @@ func (r *systemRepo) DeleteUser(ctx context.Context, id uint) error { if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil { return err } + if err := tx.Where("sys_user_id = ?", id).Delete(&userDepartmentPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_user_id = ?", id).Delete(&userPositionPO{}).Error; err != nil { + return err + } return tx.Delete(&userPO{}, id).Error }) } diff --git a/internal/data/task.go b/internal/data/task.go index 780f4bb..2a68e39 100644 --- a/internal/data/task.go +++ b/internal/data/task.go @@ -2,9 +2,10 @@ package data import ( "context" - "gorm.io/gorm" "kra/internal/biz" "time" + + "gorm.io/gorm" ) type taskPO struct { @@ -16,10 +17,10 @@ type taskPO struct { Description, Spec string WithSeconds bool ExecutorType, MethodName string - Params []byte `gorm:"type:json"` + Params jsonPO HTTPURL string HTTPMethod string - HTTPHeader []byte `gorm:"type:json"` + HTTPHeader jsonPO HTTPBody string `gorm:"type:text"` HTTPAllowPrivate, Enabled bool } @@ -45,10 +46,10 @@ type taskRepo struct{ data *Data } func NewTaskRepo(data *Data) biz.TaskRepo { return &taskRepo{data: data} } func taskToPO(v *biz.TimedTask) taskPO { - return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} + return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: jsonPO(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: jsonPO(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } func taskFromPO(v taskPO) *biz.TimedTask { - return &biz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} + return &biz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } func (r *taskRepo) CreateTask(ctx context.Context, v *biz.TimedTask) error { po := taskToPO(v) @@ -58,6 +59,15 @@ func (r *taskRepo) CreateTask(ctx context.Context, v *biz.TimedTask) error { v.ID = po.ID return nil } +func (r *taskRepo) TaskNameExists(ctx context.Context, name string, excludeID uint) (bool, error) { + var count int64 + db := r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("name = ?", name) + if excludeID > 0 { + db = db.Where("id <> ?", excludeID) + } + err := db.Count(&count).Error + return count > 0, err +} func (r *taskRepo) UpdateTask(ctx context.Context, v *biz.TimedTask) error { po := taskToPO(v) return r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error @@ -142,14 +152,15 @@ func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint } return out, total, nil } -func (r *taskRepo) CleanupLogs(ctx context.Context, before time.Time) error { +func (r *taskRepo) CleanupLogs(ctx context.Context) error { + now := time.Now() return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - if err := tx.Unscoped().Where("created_at < ?", before).Delete(&operationPO{}).Error; err != nil { + if err := tx.Unscoped().Where("created_at < ?", now.Add(-2160*time.Hour)).Delete(&operationPO{}).Error; err != nil { return err } - if err := tx.Unscoped().Where("created_at < ?", before).Delete(&loginLogPO{}).Error; err != nil { + if err := tx.Unscoped().Where("created_at < ?", now.Add(-168*time.Hour)).Delete(&jwtBlacklistPO{}).Error; err != nil { return err } - return tx.Unscoped().Where("created_at < ?", before).Delete(&taskLogPO{}).Error + return tx.Unscoped().Where("created_at < ?", now.Add(-720*time.Hour)).Delete(&taskLogPO{}).Error }) } diff --git a/internal/data/tencent_storage.go b/internal/data/tencent_storage.go new file mode 100644 index 0000000..20ffd27 --- /dev/null +++ b/internal/data/tencent_storage.go @@ -0,0 +1,117 @@ +package data + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + cos "github.com/tencentyun/cos-go-sdk-v5" + "kra/internal/biz" + "kra/internal/conf" +) + +type tencentStorage struct { + client *cos.Client + baseURL, prefix string +} + +func newTencentStorage(config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { + if config == nil || config.Bucket == "" || config.Region == "" || config.AccessKey == "" || config.SecretKey == "" { + return nil, fmt.Errorf("tencent-cos storage configuration is incomplete") + } + endpoint := config.Endpoint + if endpoint == "" { + endpoint = fmt.Sprintf("https://%s.cos.%s.myqcloud.com", config.Bucket, config.Region) + } else if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") { + if config.UseSsl { + endpoint = "https://" + endpoint + } else { + endpoint = "http://" + endpoint + } + } + bucketURL, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + client := cos.NewClient(&cos.BaseURL{BucketURL: bucketURL}, &http.Client{Transport: &cos.AuthorizationTransport{SecretID: config.AccessKey, SecretKey: config.SecretKey}}) + return &tencentStorage{client: client, baseURL: strings.TrimSuffix(config.BaseUrl, "/"), prefix: strings.Trim(config.PathPrefix, "/")}, nil +} +func (s *tencentStorage) key(name string) string { + if s.prefix == "" { + return strings.TrimPrefix(name, "/") + } + return path.Join(s.prefix, strings.TrimPrefix(name, "/")) +} +func (s *tencentStorage) unkey(key string) string { + return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") +} +func (s *tencentStorage) file(key string, size int64) *biz.StoredFile { + name := s.unkey(key) + fileURL := s.baseURL + "/" + key + return &biz.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size} +} +func (s *tencentStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { + key := s.key(name) + if _, err := s.client.Object.Put(ctx, key, reader, nil); err != nil { + return nil, err + } + response, err := s.client.Object.Head(ctx, key, nil) + if err != nil { + return nil, err + } + return s.file(key, response.ContentLength), nil +} +func (s *tencentStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) { + response, err := s.client.Object.Get(ctx, s.key(name), nil) + if err != nil { + return nil, err + } + return response.Body, nil +} +func (s *tencentStorage) Delete(ctx context.Context, name string) error { + _, err := s.client.Object.Delete(ctx, s.key(name)) + return err +} +func (s *tencentStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { + return composeFiles(ctx, s, names, destination) +} +func (s *tencentStorage) DeletePrefix(ctx context.Context, prefix string) error { + cursor := "" + for { + items, next, more, err := s.List(ctx, prefix, cursor, 1000) + if err != nil { + return err + } + for _, item := range items { + if err = s.Delete(ctx, item.Path); err != nil { + return err + } + } + if !more { + return nil + } + cursor = next + } +} +func (s *tencentStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { + if limit < 1 || limit > 1000 { + limit = 100 + } + result, _, err := s.client.Bucket.Get(ctx, &cos.BucketGetOptions{Prefix: s.key(prefix), Marker: cursor, MaxKeys: limit}) + if err != nil { + return nil, "", false, err + } + items := make([]*biz.StoredFile, 0, len(result.Contents)) + for _, object := range result.Contents { + items = append(items, s.file(object.Key, object.Size)) + } + next := result.NextMarker + if result.IsTruncated && next == "" && len(result.Contents) > 0 { + next = result.Contents[len(result.Contents)-1].Key + } + return items, next, result.IsTruncated, nil +} diff --git a/internal/server/audit_routes.go b/internal/server/audit_routes.go index 0a97967..2986bcd 100644 --- a/internal/server/audit_routes.go +++ b/internal/server/audit_routes.go @@ -45,15 +45,28 @@ func registerAuditRoutes(group, public *gin.RouterGroup, svc *service.AuditServi writeResult(c, codeSuccess, gin.H{"resysOperationRecord": item}, "查询成功") }) operations.DELETE("/deleteSysOperationRecord", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - if err := svc.DeleteOperations(c.Request.Context(), []uint{uint(id)}); err != nil { + var req struct { + ID uint `json:"ID"` + } + if c.ShouldBindJSON(&req) != nil || req.ID == 0 { + fail(c, "参数错误") + return + } + if err := svc.DeleteOperations(c.Request.Context(), []uint{req.ID}); err != nil { fail(c, "删除失败") return } ok(c) }) operations.DELETE("/deleteSysOperationRecordByIds", func(c *gin.Context) { - if err := svc.DeleteOperations(c.Request.Context(), idsFromQuery(c)); err != nil { + var req struct { + IDs []uint `json:"ids"` + } + if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 { + fail(c, "参数错误") + return + } + if err := svc.DeleteOperations(c.Request.Context(), req.IDs); err != nil { fail(c, "删除失败") return } @@ -80,15 +93,28 @@ func registerAuditRoutes(group, public *gin.RouterGroup, svc *service.AuditServi writeResult(c, codeSuccess, item, "查询成功") }) logins.DELETE("/deleteLoginLog", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - if err := svc.DeleteLogins(c.Request.Context(), []uint{uint(id)}); err != nil { + var req struct { + ID uint `json:"ID"` + } + if c.ShouldBindJSON(&req) != nil || req.ID == 0 { + fail(c, "参数错误") + return + } + if err := svc.DeleteLogins(c.Request.Context(), []uint{req.ID}); err != nil { fail(c, "删除失败") return } ok(c) }) logins.DELETE("/deleteLoginLogByIds", func(c *gin.Context) { - if err := svc.DeleteLogins(c.Request.Context(), idsFromQuery(c)); err != nil { + var req struct { + IDs []uint `json:"ids"` + } + if c.ShouldBindJSON(&req) != nil || len(req.IDs) == 0 { + fail(c, "参数错误") + return + } + if err := svc.DeleteLogins(c.Request.Context(), req.IDs); err != nil { fail(c, "删除失败") return } diff --git a/internal/server/gin.go b/internal/server/gin.go index 4d7d3b5..c0ed671 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -34,14 +34,6 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System gin.SetMode(gin.ReleaseMode) engine := gin.New() engine.Use(ginRequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit)) - localStorage := config == nil || config.Storage == nil || config.Storage.Type == "" || config.Storage.Type == "local" - if localStorage && config != nil && config.Local != nil && config.Local.StorePath != "" { - pathPrefix := "/" + strings.Trim(config.Local.PathPrefix, "/") - if pathPrefix != "/" { - engine.StaticFS(pathPrefix, filesOnly{FileSystem: http.Dir(config.Local.StorePath)}) - } - } - prefix := "" if config != nil { prefix = strings.TrimSuffix(config.RouterPrefix, "/") @@ -50,13 +42,13 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System public.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") }) - registerPublicRoutes(public, engine, config, svc, settings, access, audit) + registerPublicRoutes(public, engine, config, svc, settings, access, audit, scheduler) private := engine.Group(prefix) private.Use(jwtAuth(config, settings), mustChangePasswordGuard(), accessControl(access, audit)) - registerPrivateRoutes(private, svc) + registerPrivateRoutes(private, svc, settings) registerAccessRoutes(private, engine, access) registerSettingsRoutes(private, public, settings) - registerSystemConfigRoutes(private, config, svc, settings) + registerSystemConfigRoutes(private, config, svc, settings, scheduler) registerVersionRoutes(private, versions) registerExportRoutes(private, public, svc, exports) registerAuditRoutes(private, public, audit) @@ -66,6 +58,9 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System registerEmailRoutes(private, emails) engine.NoRoute(func(c *gin.Context) { + if serveLocalStorage(c, config) { + return + } fail(c, "请求的接口不存在") }) @@ -88,6 +83,23 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System } } +// serveLocalStorage resolves the local path for every request so a config +// reload takes effect without rebuilding the Gin engine. +func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool { + if config == nil || config.Local == nil || config.Local.StorePath == "" { + return false + } + if config.Storage != nil && config.Storage.Type != "" && config.Storage.Type != "local" { + return false + } + prefix := "/" + strings.Trim(config.Local.PathPrefix, "/") + if prefix == "/" || (c.Request.URL.Path != prefix && !strings.HasPrefix(c.Request.URL.Path, prefix+"/")) { + return false + } + http.StripPrefix(prefix, http.FileServer(filesOnly{FileSystem: http.Dir(config.Local.StorePath)})).ServeHTTP(c.Writer, c.Request) + return true +} + func jwtAuth(config *conf.AdminBackend, settings *service.SettingsService) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("x-token") @@ -107,6 +119,27 @@ func jwtAuth(config *conf.AdminBackend, settings *service.SettingsService) gin.H noAuth(c, "登录状态已失效") return } + if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second { + expires, buffer := 7*24*time.Hour, time.Duration(claims.BufferTime)*time.Second + issuer := claims.Issuer + if config != nil && config.Jwt != nil { + if config.Jwt.ExpiresTime != nil { + expires = config.Jwt.ExpiresTime.AsDuration() + } + if config.Jwt.BufferTime != nil { + buffer = config.Jwt.BufferTime.AsDuration() + } + if config.Jwt.Issuer != "" { + issuer = config.Jwt.Issuer + } + } + newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd) + if refreshErr == nil { + c.Header("new-token", newToken) + c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) + http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: newToken, Path: "/", MaxAge: int(expires.Seconds()), HttpOnly: true, SameSite: http.SameSiteStrictMode}) + } + } c.Set("admin_claims", claims) c.Next() } @@ -134,7 +167,7 @@ func mustChangePasswordGuard() gin.HandlerFunc { } } -func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService) { +func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService, scheduler *TaskScheduler) { expiration := 3 * time.Minute keyLong, width, height := 6, 240, 80 if config != nil && config.Captcha != nil { @@ -238,22 +271,31 @@ func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *co writeResult(c, codeSuccess, gin.H{"needInit": !initialized}, message) }) initGroup.POST("/initdb", func(c *gin.Context) { + initialized, err := svc.IsInitialized(c.Request.Context()) + if err != nil { + fail(c, "数据库状态检查失败") + return + } + if initialized { + fail(c, "数据库已初始化,无需重复初始化") + return + } var input service.DatabaseInit if err := c.ShouldBindJSON(&input); err != nil { fail(c, "数据库初始化参数无效") return } - if err := svc.Initialize(c.Request.Context(), &input); err != nil { - fail(c, "自动创建数据库失败: "+err.Error()) - return - } routes := engine.Routes() apis := make([]*biz.API, 0, len(routes)) for _, route := range routes { apis = append(apis, &biz.API{Path: route.Path, Method: route.Method, APIGroup: routeGroup(route.Path)}) } - if err := access.ApplyAPISync(c.Request.Context(), apis, nil); err != nil { - fail(c, "数据库已初始化,但 API 权限数据写入失败") + if err := svc.Initialize(c.Request.Context(), &input, apis); err != nil { + fail(c, "自动创建数据库失败: "+err.Error()) + return + } + if err := scheduler.Reload(c.Request.Context()); err != nil { + fail(c, "数据库已初始化,但定时任务加载失败:"+err.Error()) return } writeResult(c, codeSuccess, gin.H{}, "自动创建数据库成功") @@ -277,9 +319,8 @@ func cachedInt(ctx context.Context, svc *service.SystemService, key string) (int return result, err } func incrementCached(ctx context.Context, svc *service.SystemService, key string, ttl time.Duration) (int, error) { - value, _ := cachedInt(ctx, svc, key) - value++ - return value, svc.CacheSet(ctx, key, strconv.Itoa(value), ttl) + value, err := svc.CacheIncrement(ctx, key, ttl) + return int(value), err } func securityRateLimit(svc *service.SystemService, settings *service.SettingsService) gin.HandlerFunc { return func(c *gin.Context) { @@ -356,7 +397,7 @@ func (f filesOnly) Open(name string) (http.File, error) { return file, nil } -func registerPrivateRoutes(group *gin.RouterGroup, svc *service.SystemService) { +func registerPrivateRoutes(group *gin.RouterGroup, svc *service.SystemService, settings *service.SettingsService) { user := group.Group("/user") user.POST("/getUserList", func(c *gin.Context) { var req struct { @@ -572,6 +613,14 @@ func registerPrivateRoutes(group *gin.RouterGroup, svc *service.SystemService) { }) jwt := group.Group("/jwt") jwt.POST("/jsonInBlacklist", func(c *gin.Context) { + token := c.GetHeader("x-token") + if token == "" { + token, _ = c.Cookie("x-token") + } + if err := settings.BlacklistToken(c.Request.Context(), token); err != nil { + fail(c, "退出失败") + return + } http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true}) ok(c) }) diff --git a/internal/server/grpc.go b/internal/server/grpc.go deleted file mode 100644 index 52dec8d..0000000 --- a/internal/server/grpc.go +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index 6975c9c..0000000 --- a/internal/server/http.go +++ /dev/null @@ -1,37 +0,0 @@ -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/menu_routes.go b/internal/server/menu_routes.go index 4c1dcac..4b5c390 100644 --- a/internal/server/menu_routes.go +++ b/internal/server/menu_routes.go @@ -31,10 +31,22 @@ type menuRequest struct { Name string `json:"name"` Description string `json:"desc"` } `json:"menuBtn"` + Parameters []struct { + ID uint `json:"ID"` + Type string `json:"type"` + Key string `json:"key"` + Value string `json:"value"` + } `json:"parameters"` } func menuDomain(v menuRequest) *biz.Menu { - return &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType} + menu := &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType} + menu.Buttons = menuButtons(v, v.ID) + menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters)) + for _, parameter := range v.Parameters { + menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + return menu } func menuButtons(v menuRequest, menuID uint) []*biz.MenuButton { @@ -74,10 +86,6 @@ func registerMenuRoutes(group *gin.RouterGroup, svc *service.AccessService) { fail(c, "新增失败") return } - if err := svc.ReplaceMenuButtons(c.Request.Context(), menu.ID, menuButtons(req, menu.ID)); err != nil { - fail(c, "按钮保存失败") - return - } ok(c) }) router.POST("/updateBaseMenu", func(c *gin.Context) { @@ -90,10 +98,6 @@ func registerMenuRoutes(group *gin.RouterGroup, svc *service.AccessService) { fail(c, "更新失败") return } - if err := svc.ReplaceMenuButtons(c.Request.Context(), req.ID, menuButtons(req, req.ID)); err != nil { - fail(c, "按钮保存失败") - return - } ok(c) }) router.POST("/deleteBaseMenu", func(c *gin.Context) { diff --git a/internal/server/server.go b/internal/server/server.go index 2c4b845..dc0332d 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -5,4 +5,4 @@ import ( ) // ProviderSet is server providers. -var ProviderSet = wire.NewSet(NewGRPCServer, NewGinServer, NewTaskScheduler) +var ProviderSet = wire.NewSet(NewGinServer, NewTaskScheduler) diff --git a/internal/server/settings_routes.go b/internal/server/settings_routes.go index 1f7a6af..87eac9f 100644 --- a/internal/server/settings_routes.go +++ b/internal/server/settings_routes.go @@ -328,26 +328,24 @@ func registerParameterRoutes(group *gin.RouterGroup, svc *service.SettingsServic ok(c) }) router.DELETE("/deleteSysParams", func(c *gin.Context) { - var req parameterRequest - if c.ShouldBindJSON(&req) != nil { + id, err := strconv.ParseUint(c.Query("ID"), 10, 64) + if err != nil || id == 0 { fail(c, "参数错误") return } - if err := svc.DeleteParameters(c.Request.Context(), []uint{req.ID}); err != nil { + if err := svc.DeleteParameters(c.Request.Context(), []uint{uint(id)}); err != nil { fail(c, "删除失败") return } ok(c) }) router.DELETE("/deleteSysParamsByIds", func(c *gin.Context) { - var req struct { - IDs []uint `json:"ids"` - } - if c.ShouldBindJSON(&req) != nil { + ids := idsFromQuery(c) + if len(ids) == 0 { fail(c, "参数错误") return } - if err := svc.DeleteParameters(c.Request.Context(), req.IDs); err != nil { + if err := svc.DeleteParameters(c.Request.Context(), ids); err != nil { fail(c, "删除失败") return } @@ -368,7 +366,7 @@ func registerParameterRoutes(group *gin.RouterGroup, svc *service.SettingsServic fail(c, "查询失败") return } - writeResult(c, codeSuccess, gin.H{"data": item}, "查询成功") + writeResult(c, codeSuccess, item, "查询成功") }) router.GET("/getSysParamsList", func(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/internal/server/system_config_routes.go b/internal/server/system_config_routes.go index 7f2dc94..9e54850 100644 --- a/internal/server/system_config_routes.go +++ b/internal/server/system_config_routes.go @@ -15,7 +15,7 @@ import ( "google.golang.org/protobuf/proto" ) -func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService) { +func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService, scheduler *TaskScheduler) { security := group.Group("/securityConfig") security.GET("/getSecurityConfig", func(c *gin.Context) { value, err := settings.Security(c.Request.Context()) @@ -54,6 +54,9 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken if config.Local != nil { admin["local"] = gin.H{"storePath": config.Local.StorePath, "pathPrefix": config.Local.PathPrefix} } + if config.Media != nil { + admin["media"] = gin.H{"sessionTtl": config.Media.SessionTtl} + } if config.Email != nil { email = gin.H{"to": config.Email.To, "from": config.Email.From, "host": config.Email.Host, "secret": "******", "nickname": config.Email.Nickname, "port": config.Email.Port, "is-ssl": config.Email.IsSsl, "is-loginauth": config.Email.IsLoginAuth} } @@ -78,6 +81,9 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken StorePath string `json:"storePath"` PathPrefix string `json:"pathPrefix"` } `json:"local"` + Media struct { + SessionTTL int32 `json:"sessionTtl"` + } `json:"media"` Storage *conf.AdminBackend_Storage `json:"storage"` } `json:"admin"` Email *struct { @@ -97,48 +103,60 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken return } if config != nil { - config.RouterPrefix = req.Config.Admin.RouterPrefix - if config.Jwt != nil { + next := proto.Clone(config).(*conf.AdminBackend) + next.RouterPrefix = req.Config.Admin.RouterPrefix + if next.Jwt != nil { if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" { - config.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey + next.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey } if req.Config.Admin.JWT.Issuer != "" { - config.Jwt.Issuer = req.Config.Admin.JWT.Issuer + next.Jwt.Issuer = req.Config.Admin.JWT.Issuer } } - if config.Local != nil { + if next.Local != nil { if req.Config.Admin.Local.StorePath != "" { - config.Local.StorePath = req.Config.Admin.Local.StorePath + next.Local.StorePath = req.Config.Admin.Local.StorePath } if req.Config.Admin.Local.PathPrefix != "" { - config.Local.PathPrefix = req.Config.Admin.Local.PathPrefix + next.Local.PathPrefix = req.Config.Admin.Local.PathPrefix } } + if next.Media != nil && req.Config.Admin.Media.SessionTTL > 0 { + next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL + } if req.Config.Admin.Storage != nil { - preserveStorageSecrets(req.Config.Admin.Storage, config.Storage) - config.Storage = req.Config.Admin.Storage + preserveStorageSecrets(req.Config.Admin.Storage, next.Storage) + next.Storage = req.Config.Admin.Storage } - if config.Email != nil && req.Config.Email != nil { - config.Email.To = req.Config.Email.To - config.Email.From = req.Config.Email.From - config.Email.Host = req.Config.Email.Host - config.Email.Nickname = req.Config.Email.Nickname - config.Email.Port = req.Config.Email.Port - config.Email.IsSsl = req.Config.Email.IsSSL - config.Email.IsLoginAuth = req.Config.Email.IsLoginAuth + if next.Email != nil && req.Config.Email != nil { + next.Email.To = req.Config.Email.To + next.Email.From = req.Config.Email.From + next.Email.Host = req.Config.Email.Host + next.Email.Nickname = req.Config.Email.Nickname + next.Email.Port = req.Config.Email.Port + next.Email.IsSsl = req.Config.Email.IsSSL + next.Email.IsLoginAuth = req.Config.Email.IsLoginAuth if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" { - config.Email.Secret = req.Config.Email.Secret + next.Email.Secret = req.Config.Email.Secret } } + if err := systemService.PersistAdminConfig(c.Request.Context(), next); err != nil { + fail(c, "配置保存失败: "+err.Error()) + return + } } - if err := systemService.PersistConfig(c.Request.Context()); err != nil { - fail(c, "配置保存失败: "+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{"restartRequired": true}, "配置已保存,重启服务后完整生效") + writeResult(c, codeSuccess, gin.H{}, "设置成功") }) system.POST("/reloadSystem", func(c *gin.Context) { - writeResult(c, codeSuccess, gin.H{"reload": false, "restartRequired": true}, "配置已在当前进程部分生效;路由与存储配置需重启服务") + if err := systemService.ReloadConfig(c.Request.Context()); err != nil { + fail(c, "重载系统失败:"+err.Error()) + return + } + if err := scheduler.Reload(c.Request.Context()); err != nil { + fail(c, "系统配置已重载,但定时任务重载失败:"+err.Error()) + return + } + writeResult(c, codeSuccess, gin.H{}, "重载系统成功") }) system.POST("/getServerInfo", func(c *gin.Context) { cpuPercent, _ := cpu.Percent(200*time.Millisecond, false) diff --git a/internal/server/task_routes.go b/internal/server/task_routes.go index d2e32c1..3c671b5 100644 --- a/internal/server/task_routes.go +++ b/internal/server/task_routes.go @@ -69,9 +69,15 @@ func registerTaskRoutes(group *gin.RouterGroup, svc *service.TaskService, schedu ok(c) }) router.DELETE("/deleteTimedTask", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - scheduler.Remove(uint(id)) - if err := svc.Delete(c.Request.Context(), uint(id)); err != nil { + var req struct { + ID uint `json:"ID"` + } + if c.ShouldBindJSON(&req) != nil || req.ID == 0 { + fail(c, "参数错误") + return + } + scheduler.Remove(req.ID) + if err := svc.Delete(c.Request.Context(), req.ID); err != nil { fail(c, "删除失败") return } diff --git a/internal/server/task_scheduler.go b/internal/server/task_scheduler.go index ac28384..99c98ef 100644 --- a/internal/server/task_scheduler.go +++ b/internal/server/task_scheduler.go @@ -76,6 +76,35 @@ func (s *TaskScheduler) Remove(id uint) { delete(s.entries, id) } } +func (s *TaskScheduler) Reload(ctx context.Context) error { + s.mu.Lock() + for id, old := range s.entries { + if old.seconds { + s.seconds.Remove(old.entry) + } else { + s.standard.Remove(old.entry) + } + delete(s.entries, id) + } + s.mu.Unlock() + items, _, err := s.service.Tasks(ctx, 1, 100000, nil, nil) + if err != nil { + return err + } + for _, raw := range items { + id, _ := raw["ID"].(uint) + task, findErr := s.service.Task(ctx, id) + if findErr != nil { + return findErr + } + if task.Enabled { + if err = s.Schedule(task); err != nil { + return err + } + } + } + return nil +} func (s *TaskScheduler) Schedule(task *biz.TimedTask) error { s.Remove(task.ID) if !task.Enabled { diff --git a/internal/service/admin.go b/internal/service/admin.go deleted file mode 100644 index 9268227..0000000 --- a/internal/service/admin.go +++ /dev/null @@ -1,232 +0,0 @@ -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 index 376da4a..5204d6e 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -3,4 +3,4 @@ package service import "github.com/google/wire" // ProviderSet is service providers. -var ProviderSet = wire.NewSet(NewAdminService, NewSystemService, NewAccessService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) +var ProviderSet = wire.NewSet(NewSystemService, NewAccessService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) diff --git a/internal/service/settings.go b/internal/service/settings.go index 96680b1..15ae104 100644 --- a/internal/service/settings.go +++ b/internal/service/settings.go @@ -162,13 +162,17 @@ func (s *SettingsService) CreateAPIToken(ctx context.Context, userID, authorityI return "", errors.New("有效天数必须大于0或为-1") } secret, issuer := "", "kra" + buffer := 24 * time.Hour if s.config != nil && s.config.Jwt != nil { secret = s.config.Jwt.SigningKey if s.config.Jwt.Issuer != "" { issuer = s.config.Jwt.Issuer } + if s.config.Jwt.BufferTime != nil { + buffer = s.config.Jwt.BufferTime.AsDuration() + } } - token, _, err := adminauth.Generate(secret, issuer, duration, user.ID, authorityID, user.UUID, user.Username, user.NickName, false) + token, _, err := adminauth.Generate(secret, issuer, duration, buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, false) if err != nil { return "", err } @@ -190,8 +194,14 @@ func (s *SettingsService) APITokens(ctx context.Context, page, size int, userID return out, total, nil } func (s *SettingsService) DisableAPIToken(ctx context.Context, id uint) error { - _, err := s.Repo().DisableAPIToken(ctx, id) - return err + token, err := s.Repo().DisableAPIToken(ctx, id) + if err != nil { + return err + } + return s.Repo().BlacklistToken(ctx, token) +} +func (s *SettingsService) BlacklistToken(ctx context.Context, token string) error { + return s.Repo().BlacklistToken(ctx, token) } func (s *SettingsService) IsTokenDisabled(ctx context.Context, token string) (bool, error) { return s.Repo().IsTokenDisabled(ctx, token) diff --git a/internal/service/system.go b/internal/service/system.go index 7cddf4f..e6fa628 100644 --- a/internal/service/system.go +++ b/internal/service/system.go @@ -4,6 +4,7 @@ import ( "context" "time" + "google.golang.org/protobuf/encoding/protojson" "kra/internal/biz" "kra/internal/conf" "kra/pkg/adminauth" @@ -30,6 +31,14 @@ func (s *SystemService) IsInitialized(ctx context.Context) (bool, error) { return s.uc.IsInitialized(ctx) } func (s *SystemService) PersistConfig(ctx context.Context) error { return s.uc.PersistConfig(ctx) } +func (s *SystemService) PersistAdminConfig(ctx context.Context, value *conf.AdminBackend) error { + raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(value) + if err != nil { + return err + } + return s.uc.PersistAdminConfig(ctx, raw) +} +func (s *SystemService) ReloadConfig(ctx context.Context) error { return s.uc.ReloadConfig(ctx) } type DatabaseInit struct { DBType string `json:"dbType"` @@ -43,7 +52,7 @@ type DatabaseInit struct { AdminPassword string `json:"adminPassword"` } -func (s *SystemService) Initialize(ctx context.Context, input *DatabaseInit) error { +func (s *SystemService) Initialize(ctx context.Context, input *DatabaseInit, apis []*biz.API) error { config := "" switch input.DBType { case "mysql": @@ -51,7 +60,7 @@ func (s *SystemService) Initialize(ctx context.Context, input *DatabaseInit) err case "pgsql": config = "sslmode=disable TimeZone=Local" } - return s.uc.Initialize(ctx, &biz.DatabaseConfig{Driver: input.DBType, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, AdminPassword: input.AdminPassword}) + return s.uc.Initialize(ctx, &biz.DatabaseConfig{Driver: input.DBType, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, Template: input.Template, AdminPassword: input.AdminPassword, APIs: apis}) } func (s *SystemService) CacheGet(ctx context.Context, key string) (string, bool, error) { @@ -63,6 +72,9 @@ func (s *SystemService) CacheSet(ctx context.Context, key, value string, expirat func (s *SystemService) CacheDelete(ctx context.Context, key string) error { return s.uc.CacheDelete(ctx, key) } +func (s *SystemService) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) { + return s.uc.CacheIncrement(ctx, key, expiration) +} func (s *SystemService) Login(ctx context.Context, username, password string) (*LoginResult, error) { u, err := s.uc.Login(ctx, username, password) @@ -75,15 +87,18 @@ func (s *SystemService) Login(ctx context.Context, username, password string) (* if security, securityErr := s.settings.CurrentSecurity(ctx); securityErr == nil && security.PwdExpireEnable && u.PasswordUpdatedAt != nil && time.Since(*u.PasswordUpdatedAt) > time.Duration(security.PwdExpireDays)*24*time.Hour { u.MustChangePassword = true } - expires := 7 * 24 * time.Hour + expires, buffer := 7*24*time.Hour, 24*time.Hour secret, issuer := "", "kra" if s.config != nil && s.config.Jwt != nil { secret, issuer = s.config.Jwt.SigningKey, s.config.Jwt.Issuer if s.config.Jwt.ExpiresTime != nil { expires = s.config.Jwt.ExpiresTime.AsDuration() } + if s.config.Jwt.BufferTime != nil { + buffer = s.config.Jwt.BufferTime.AsDuration() + } } - token, claims, err := adminauth.Generate(secret, issuer, expires, u.ID, u.AuthorityID, u.UUID, u.Username, u.NickName, u.MustChangePassword) + token, claims, err := adminauth.Generate(secret, issuer, expires, buffer, u.ID, u.AuthorityID, u.UUID, u.Username, u.NickName, u.MustChangePassword) if err != nil { return nil, err } @@ -212,15 +227,18 @@ func (s *SystemService) SwitchAuthority(ctx context.Context, id, authorityID uin if err != nil { return nil, err } - expires := 7 * 24 * time.Hour + expires, buffer := 7*24*time.Hour, 24*time.Hour secret, issuer := "", "kra" if s.config != nil && s.config.Jwt != nil { secret, issuer = s.config.Jwt.SigningKey, s.config.Jwt.Issuer if s.config.Jwt.ExpiresTime != nil { expires = s.config.Jwt.ExpiresTime.AsDuration() } + if s.config.Jwt.BufferTime != nil { + buffer = s.config.Jwt.BufferTime.AsDuration() + } } - token, claims, err := adminauth.Generate(secret, issuer, expires, u.ID, authorityID, u.UUID, u.Username, u.NickName, u.MustChangePassword) + token, claims, err := adminauth.Generate(secret, issuer, expires, buffer, u.ID, authorityID, u.UUID, u.Username, u.NickName, u.MustChangePassword) if err != nil { return nil, err } @@ -260,8 +278,16 @@ func convertMenu(menu *biz.Menu) map[string]any { children = append(children, convertMenu(child)) } buttons := make([]map[string]any, 0, len(menu.Buttons)) + buttonAuthorities := make(map[string]uint, len(menu.Buttons)) for _, button := range menu.Buttons { buttons = append(buttons, map[string]any{"ID": button.ID, "name": button.Name, "desc": button.Description, "sysBaseMenuID": button.MenuID}) + if button.AuthorityID != 0 { + buttonAuthorities[button.Name] = button.AuthorityID + } } - return map[string]any{"ID": menu.ID, "parentId": menu.ParentID, "path": menu.Path, "name": menu.Name, "hidden": menu.Hidden, "component": menu.Component, "sort": menu.Sort, "meta": map[string]any{"activeName": menu.ActiveName, "keepAlive": menu.KeepAlive, "defaultMenu": menu.DefaultMenu, "title": menu.Title, "icon": menu.Icon, "closeTab": menu.CloseTab, "transitionType": menu.TransitionType}, "children": children, "parameters": []any{}, "menuBtn": buttons} + parameters := make([]map[string]any, 0, len(menu.Parameters)) + for _, parameter := range menu.Parameters { + parameters = append(parameters, map[string]any{"ID": parameter.ID, "SysBaseMenuID": parameter.MenuID, "type": parameter.Type, "key": parameter.Key, "value": parameter.Value}) + } + return map[string]any{"ID": menu.ID, "parentId": menu.ParentID, "path": menu.Path, "name": menu.Name, "hidden": menu.Hidden, "component": menu.Component, "sort": menu.Sort, "meta": map[string]any{"activeName": menu.ActiveName, "keepAlive": menu.KeepAlive, "defaultMenu": menu.DefaultMenu, "title": menu.Title, "icon": menu.Icon, "closeTab": menu.CloseTab, "transitionType": menu.TransitionType}, "children": children, "parameters": parameters, "menuBtn": buttons, "btns": buttonAuthorities} } diff --git a/internal/service/task.go b/internal/service/task.go index bdc0eb7..3375e1c 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -9,6 +9,7 @@ import ( "github.com/robfig/cron/v3" "io" "kra/internal/biz" + "kra/internal/conf" "net" "net/http" "net/url" @@ -16,9 +17,15 @@ import ( "time" ) -type TaskService struct{ uc *biz.TaskUsecase } +type TaskService struct { + uc *biz.TaskUsecase + media *biz.MediaUsecase + config *conf.AdminBackend +} -func NewTaskService(uc *biz.TaskUsecase) *TaskService { return &TaskService{uc: uc} } +func NewTaskService(uc *biz.TaskUsecase, media *biz.MediaUsecase, config *conf.AdminBackend) *TaskService { + return &TaskService{uc: uc, media: media, config: config} +} func (s *TaskService) Validate(v *biz.TimedTask) error { if v.Name == "" { return errors.New("任务名不能为空") @@ -34,7 +41,7 @@ func (s *TaskService) Validate(v *biz.TimedTask) error { } switch v.ExecutorType { case "method": - if v.MethodName != "ClearDB" { + if v.MethodName != "ClearDB" && v.MethodName != "CleanStaleUploads" { return errors.New("方法未注册") } if len(v.Params) > 0 && !json.Valid(v.Params) { @@ -60,6 +67,13 @@ func (s *TaskService) Create(ctx context.Context, v *biz.TimedTask) error { if err := s.Validate(v); err != nil { return err } + exists, err := s.uc.Repo().TaskNameExists(ctx, v.Name, 0) + if err != nil { + return err + } + if exists { + return fmt.Errorf("任务名 %s 已存在", v.Name) + } return s.uc.Repo().CreateTask(ctx, v) } func (s *TaskService) Update(ctx context.Context, v *biz.TimedTask) error { @@ -69,6 +83,13 @@ func (s *TaskService) Update(ctx context.Context, v *biz.TimedTask) error { if err := s.Validate(v); err != nil { return err } + exists, err := s.uc.Repo().TaskNameExists(ctx, v.Name, v.ID) + if err != nil { + return err + } + if exists { + return fmt.Errorf("任务名 %s 已存在", v.Name) + } return s.uc.Repo().UpdateTask(ctx, v) } func (s *TaskService) Delete(ctx context.Context, id uint) error { @@ -168,17 +189,17 @@ func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string) var err error switch v.ExecutorType { case "method": - if v.MethodName == "ClearDB" { - days := 30 - var params struct { - Days int `json:"days"` - } - _ = json.Unmarshal(v.Params, ¶ms) - if params.Days > 0 { - days = params.Days - } - err = s.uc.Repo().CleanupLogs(runCtx, time.Now().AddDate(0, 0, -days)) + switch v.MethodName { + case "ClearDB": + err = s.uc.Repo().CleanupLogs(runCtx) log.Output = "过期日志清理完成" + case "CleanStaleUploads": + ttl := 24 + if s.config != nil && s.config.Media != nil && s.config.Media.SessionTtl > 0 { + ttl = int(s.config.Media.SessionTtl) + } + err = s.media.CleanupStale(runCtx, ttl) + log.Output = "过期大文件上传会话清理完成" } case "http": log.Output, err = s.runHTTP(runCtx, v) @@ -190,5 +211,5 @@ func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string) return log } func (s *TaskService) RegisteredMethods() []map[string]any { - return []map[string]any{{"name": "ClearDB", "description": "清理数据库过期日志(操作记录、登录日志、定时任务日志)"}} + return []map[string]any{{"name": "ClearDB", "description": "清理数据库过期日志(操作记录、JWT黑名单、定时任务日志)"}, {"name": "CleanStaleUploads", "description": "清理过期大文件上传会话"}} } diff --git a/openapi.yaml b/openapi.yaml deleted file mode 100644 index 520190f..0000000 --- a/openapi.yaml +++ /dev/null @@ -1,249 +0,0 @@ -# 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/adminauth/token.go b/pkg/adminauth/token.go index 6efe25b..49f6fa5 100644 --- a/pkg/adminauth/token.go +++ b/pkg/adminauth/token.go @@ -18,12 +18,12 @@ type Claims struct { jwt.RegisteredClaims } -func Generate(secret, issuer string, expires time.Duration, userID, authorityID uint, uuid, username, nickname string, mustChange bool) (string, *Claims, error) { +func Generate(secret, issuer string, expires, buffer time.Duration, userID, authorityID uint, uuid, username, nickname string, mustChange bool) (string, *Claims, error) { if secret == "" { return "", nil, errors.New("empty JWT signing key") } now := time.Now() - claims := &Claims{UUID: uuid, ID: userID, Username: username, NickName: nickname, AuthorityID: authorityID, MustChangePwd: mustChange, RegisteredClaims: jwt.RegisteredClaims{Issuer: issuer, IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now.Add(-time.Second)), ExpiresAt: jwt.NewNumericDate(now.Add(expires))}} + claims := &Claims{UUID: uuid, ID: userID, Username: username, NickName: nickname, AuthorityID: authorityID, BufferTime: int64(buffer / time.Second), MustChangePwd: mustChange, RegisteredClaims: jwt.RegisteredClaims{Issuer: issuer, IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now.Add(-time.Second)), ExpiresAt: jwt.NewNumericDate(now.Add(expires))}} token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret)) return token, claims, err } diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go deleted file mode 100644 index 0d51dde..0000000 --- a/pkg/auth/auth.go +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 1985eab..0000000 --- a/pkg/auth/config.go +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 14b88b9..0000000 --- a/pkg/auth/middleware.go +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index bc32063..0000000 --- a/pkg/auth/middleware_test.go +++ /dev/null @@ -1,125 +0,0 @@ -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 deleted file mode 100644 index 4d793e4..0000000 --- a/pkg/auth/token.go +++ /dev/null @@ -1,48 +0,0 @@ -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/web/src/assets/cover-redesign.svg b/web/src/assets/cover-redesign.svg index 22d7ff0..8940c19 100644 --- a/web/src/assets/cover-redesign.svg +++ b/web/src/assets/cover-redesign.svg @@ -153,7 +153,7 @@ fill="rgba(255,255,255,.9)">GO images / GinVueAdmin.go + fill="rgba(255,255,255,.6)">images / Kra.go @@ -169,7 +169,7 @@ 5 - type GinVueAdmin struct { + type Kra struct { gorm.Model diff --git a/web/src/view/layout/search/search.vue b/web/src/view/layout/search/search.vue index 64b8143..012bab2 100644 --- a/web/src/view/layout/search/search.vue +++ b/web/src/view/layout/search/search.vue @@ -6,7 +6,6 @@ @click="handleReload" /> -
{ - window.open('https://support.qq.com/product/371961') - } - const handleDarkSwitch = (e) => { isDark.value = e } diff --git a/web/src/view/systemTools/system/system.vue b/web/src/view/systemTools/system/system.vue index fe58b4a..881484b 100644 --- a/web/src/view/systemTools/system/system.vue +++ b/web/src/view/systemTools/system/system.vue @@ -29,6 +29,9 @@ + + + @@ -58,7 +61,7 @@
-

对象存储客户端在服务启动时创建,保存后需重启服务生效。

+

保存后点击“重载服务”,新的对象存储配置会立即生效。

@@ -112,7 +115,7 @@ admin: { routerPrefix: '', jwt: { signingKey: '******', issuer: 'kra' }, - local: { storePath: 'uploads/file', pathPrefix: 'uploads/file' }, + local: { storePath: 'uploads/file', pathPrefix: 'uploads/file' }, media: { sessionTtl: 24 }, storage: { type: 'local', qiniu: {}, aliyun_oss: {}, huawei_obs: {}, tencent_cos: {}, aws_s3: {}, cloudflare_r2: {}, minio: {} } }, email: { @@ -136,6 +139,7 @@ admin: { ...config.value.admin, ...res.data.config.admin, jwt: { ...config.value.admin.jwt, ...res.data.config.admin?.jwt }, local: { ...config.value.admin.local, ...res.data.config.admin?.local } + , media: { ...config.value.admin.media, ...res.data.config.admin?.media } , storage: { ...config.value.admin.storage, ...res.data.config.admin?.storage, qiniu: { ...config.value.admin.storage.qiniu, ...res.data.config.admin?.storage?.qiniu }, aliyun_oss: { ...config.value.admin.storage.aliyun_oss, ...res.data.config.admin?.storage?.aliyun_oss },